Unit Testing in Go Language. And a look at how to use Mocks | by Karthik KN

Photo by Sigmund on Unsplash

Unit testing(UT) plays a vital role in software development. It’s always better to have unit tests for each and every block of code in a program. Here we will cover the following topics:

  1. Few advantages of unit testing
  2. Example program to write unit tests
  3. Points to remember before writing unit tests
  4. Writing unit tests

As there are many advantages of unit testing, let’s list a couple of them:

  1. In software development, change is expected to be constant. Unit testing helps in the early identification of breakage in existing functionality upon the addition of new features.
  2. Unit testing helps the developer in early resolving of any bugs as they will have a better context on what’s being developed and what has been broken rather than waiting till the bug is reported by the testing team in a later stage.

With this introduction, let’s move towards a unit test case implementation in Go Language.


type Client interface {
GetSum(int, int) (int, error)
}
type myStruct struct {
}
func (input myStruct) GetSum( num1, num2 int) (int, error) {
return num1+num2, nil
}

Here we have defined an interface Client which has only a GetSum function defined in it, also we have defined a structure with name myStruct which implements the Client interface.

When we say a structure implements an interface it means the structure should implement all the methods defined in the interface in our case it’s GetSum function.

func processInput(input Client, num1, num2 int) (int, error){
res, err := input.GetSum(num1, num2)
if err != nil{
return 0, err
}
return res, nil
}

Here we have defined a function named processInput which takes a Client interface and two numbers as inputs and calls the GetSum method associated with the input interface.

func main() {
myVariable := myStruct{}
num1 := 1
num2 := 2
res, err := processInput(myVariable, num1,num2)
if err != nil{
panic(err)
}else {
fmt.Printf("Sum of %d and %d is %dn", num1, num2, res)
}
}

Finally from the main function we are calling the processInput function we appropriate parameters. The full program can be found on my GitHub.

Before getting into the actual implementation of unit tests, we should be very clear with the following points:

1. Defining the scope of a unit

Sometimes it becomes harder to identify a scope of a unit, leading it to testing more than a unit at once.

The simple rule to be followed is any code workflow whose result is not in our control is not to be included in unit testing.

In our case the function processInput is making a call to GetSum method to calculate the sum, We should not be bothered about how the sum is calculated rather we should focus on the result returned by the method GetSum.

2. Taking care of dependencies

As mentioned earlier we should only care about the results returned by the external APIs, we need to test our program against all the possible return values ​​from the external APIs, This creates a need for us to mimic the behavior of external APIs with our return values.

This can be achieved easily in go language either by manually overriding the interface methods or using the mocks. Mocks are best suited when the interface is large and also it provides various built-in methods to alter the behavior.

3. Defining test easy functions or methods

While writing a program sometimes we need to foresee the future to make the code easy for testing

In our case the function processInput we are passing the Client interface as a first parameter, this allows us in a later cases to pass any custom interface which has a method suits our requirement:

func main() {
myVariable := myStruct{}
num1 := 1
num2 := 2
myVariable.GetSum(num1, num2)
res, err := myVariable.GetSum(num1, num2)
if err != nil{
panic(err)
}else {
fmt.Printf("Sum of %d and %d is %dn", num1, num2, res)
}
}

Here we are directly calling the GetSum method instead of calculating results via processInput. But this approach works but doesn’t provide the extra flexibility to test our program.

In Go language to get started with testing we need only one package named testingthough there are many other packages are available to enrich the testing but to keep things simple let us consider examples using testing package.

Simple Unit Testing

To begin with, let’s write a simple unit test.

Here we’re calling the processInput method with 1 and 2 inputs and expect 3 as the output. If the return result is not 3 which is expected we fail the test.

func TestProcessInput(t *testing.T)  {
myVariable := myStruct{}
res, err := processInput(myVariable, 1,2)
if err != nil {
t.Fatal(err)
}
if res != 3{
t.Fatalf("Expected result %d got %d", 3, res)
}
}

2. Unit testing with the tabular method

The above simple unit testing example was good when we want to test the function for a few input combinations.

But, the following tabular method of testing is better when we define the cases and inputs and iterate over each of them to run the test. To do so, we make use of the function Run provided by the testing package.

func TestProcessInputTabularMethod(t *testing.T) {

cases := []struct {
name string
num1 int
num2 int
res int
expectError bool
}{
{
name: "Case 1",
num1: 1,
num2: 2,
res: 3,
expectError: false,
},
{
name: "Case 2",
num1: 100,
num2: 200,
res: 300,
expectError: false,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
myVariable := myStruct{}
res, err := processInput(myVariable, tc.num1,tc.num2)
if err != nil && !tc.expectError{
t.Fatal(err)
}
if res != tc.res{
t.Fatalf("Expected result %d got %d", tc.res, res)
}
})
}
}

3. Testing with custom methods for interface function

As mentioned earlier that we have to take care of the dependencies while writing unit testing. Our processInput function has the following signature:

func processInput(input client.Client, num1, num2 int) (int, error)

The first parameter to the function is the Client interface this provides the extra ability to pass any structure to this function which satisfies the Client interface, so we can add any custom behavior for the Client interface function for our testing.

type testStruct struct {

}

func (input testStruct) GetSum( num1, num2 int) (int, error) {
return 0, fmt.Errorf(" Intentional error ")
}

func TestProcessInputCustomMethods(t *testing.T) {
myVariable := testStruct{}
res, err := processInput(myVariable, 1,2)
if err == nil {
t.Fatal("Expecting error! got nil")
}
if res != 0{
t.Fatalf("Expected 0 found %d", res)
}
}

Here we are defining a structure with a name testStruct and defining GetSum method against it and with intention this method always returns an error. This helps in testing for negative scenarios for processInput function.

4. Generating Mock methods

Defining custom interface methods will help in testing but when the interface is very large we cannot spend time defining all the methods and various behavior for each method.

The above problem can be taken care of by using Mocks. We will use a package mockgen to create a mock method for our interface Client.

func TestProcessInputMocks(t *testing.T){
mockCtrl := gomock.NewController

Leave a Comment