Golang Basic Unit Testing

Aditya Rama
Nerd For Tech
Published in
6 min readFeb 28, 2021

--

Credits to NeONBRAND (https://unsplash.com/photos/60krlMMeWxU)

In a nutshell basically you need to test the code you write to ensure you’re doing it right. If you have a function that add two numbers, you will expect the result to be sum of those two numbers, by writing the unit test, you make sure that when the function is being ran with the given parameters, it will return the correct / expected result or behavior.

This writing will cover up only the basic of golang unit testing with some example and explanation, more advance and complex unit testing / even mocking a function will be covered in another article.

Function and Unit Testing

Disclaimer: I won’t be discussing TDD, this will be focused on how to create the unit test if you have a function and you want to test it.

Let say we have a package “user” with only one file (user.go) in which consists of several things:

  1. Structure of a “person”
  2. Methods for a “person”
  3. Some errors related to this user package and person struct

This type person can have attributes of:

  • Name (string)
  • Friends (slice of Person)

We’re going to attach some methods also into the person struct:

  • ChangeName(name string)
  • AddNewFriend(friend Person)

Starting from the simple one, let say we’re implementing the ChangeName function like these:

package userimport "errors"var (    ErrorPersonNameEmpty = errors.New("Person name must exist"))type Person struct {   Name    string   Friends []Person}func (p *Person) ChangeName(name string) error {
if name == "" {
return ErrorPersonNameEmpty } p.Name = name

return nil
}

save it as “user.go” in the “user” package, then create a new file “user_test.go”. Since we want to test the ChangeName function, we create a function of TestPerson_ChangeName like these:

package userimport (  “testing”)func TestPerson_ChangeName(t *testing.T) {  p := &Person{    Name: “Wuzz”,  }  p.ChangeName(“Bert”)  if err != nil {    t.Errorf("ChangeName fail, expecting nil error got %v", err)  }  
if p.Name != “Bert” {
t.Errorf(“ChangeName fail, expecting %v, got %v”, “Bert”, p.Name) }}

We create a person pointer object with “Wuzz” as its name, then we call the ChangeName method for the person with “Bert” as new name, then we compare the error returned by the function with nil since we’re expecting no error when changing name to “Bert”. After that, we compare the person name, last we expect the name of the person object changed to “Bert”, raise a testing error if the expectation is not met with additional message that print the difference between our expectation and what value we got.

I’m using visual code, and it’s pretty simple to test the unit test we created by clicking run test above the function. Alternatively you can run it manually via terminal with “go test -run ^YourTestName”. You can also run all unit test in your current project by using this basic command go test ./... in your root project folder. If it’s printing the “ok” then it means unit test is passed, let say we change the expectation in the line number 13 to something else by if p.Name != “Abcd" along side with the line 19 for error message.

Thus, the unit test will fail with error message of what we described in the line 14 due to wrong expectation. Now, we need to cover up the test case when we change the name to empty string since our logic includes a checking when the name is empty string, it will return an error. We have two alternatives, create a new Test function (ie: “TestPerson_ChangeNameToEmpty) with different logic and expectation, OR even better we can create more dynamic testing function that allows us to modify the parameter and expectation. Try to modify the unit test like this:

We changed it by creating slice of anonymous struct that will group our test cases one by one, each one will have its own parameters, test name, and output expectation. Then we’re going to loop the test cases, run the test case via tt.Run , create person object, then change the name with current test case new name, and assert the expectation accordingly.

Rerun the test again and it will pass. This structure is better than the previous one because now we can add new test case in the array of struct object. Go ahead and add new test case on the test case slice for the ChangeName with empty string parameter. We’re expecting the person’s name not to change and the returned error to be ErrorPersonNampeEmpty error type.

Now let’s move on to the second person method, create AddNewFriend method that will append a person in the parameter for this person Friends attribute.

func (p *Person) AddNewFriend(friend Person) {    p.Friends = append(p.Friends, friend)}

As you can see, the test will be a simple one like you create a person, add new friend, pass in another person object (as its friend), then we need to assert that the number of friends for this person is incrementing, and its new friends exist in the attribute.

We can write it like this

We have parameter of multiple friends, then on the test case we add them as its friends, then in the end we expect the friends of Person (Mark) will be the same as our new friends parameter. We’re using DeepEqual to ensure the friends array and its corresponding values are equal. Run the test case again and we’re going to see it pass. Let say now for some reason, we modify the function AddNewFriend and insert a logic like this.

func (p *Person) AddNewFriend(friend Person) {  if friend.Name != “Bob” {    p.Friends = append(p.Friends, friend)  }}

If the friend’s name is Bob, we’re not going to add as new friend (Bob is toxic, we don’t want toxic friend FOR EXAMPLE only, Love you Bob). Run the test again and we’re going to see it fails.

it fails due to Bob is not added as friend but we’re expecting the friends to be Claire and Bob

This is good due to the reason that if somebody is modifying our function, they might not know the effect or any edge case expected by the function result, so having a strong unit test and test cases in it can prevent someone or even yourself to modify something and breaks any existing feature / flow. You can know by the failing test case on your related function.

Hope it’s useful for you, we’re going to more complex assertion and mocking on another article. Have a nice day :)

--

--