Golang Write Array of Unit Test Cases while Mocking the Interfaces

Damindu Lakmal
4 min readApr 23, 2023

--

header

Unit testing is a way of testing code that can be a logically isolated function or method. Golang project should be fully tested before go live because micro-services architecture will be brake with lesser code coverage. Testing will help you to expose bugs that find their way in as you make changes.

Golang has builtin library which support for writing unit tests as you go. It is intended to be used in concert with the “go test” command, which automates execution of any function of the form,

func TestXxx(*testing.T)

You can keep test cases in separate file that file name should be suffix as “_test.go”. After generating a new test file, you will be able to change the package as below,

package xxx_test

Test Cases

Test case can be written in two ways such as,

  • Single test case
  • Array of test cases

Singel Test Case

Test case attribute declare as a single test unit before executing testable function or method.

func TestXxx(t *testing.T) {
// test attribute declaration
// here
out,err := testableFunction(in)
if err!=nil{
t.Errro(err)
}
}

Array of Test Cases

Map of test case structs are used for implementing array of test cases. It is easy to organise different test scenarios in single test function.

func TestXxx(t *testing.T) {
tests := map[string]struct{
in interface{}
out interface{}
wantError bool
}{
`test case 1` :{
in input1,
out output1,
wantError false,
},
// more test case
}

// test attribute declaration
// here
for name, test := range tests {
t.Run(name, func(t *testing.T) {
out,err := testableFunction()
if err!=nil{
t.Errro(err)
}
})
}
}

GoMock Install

gomock is a mocking framework for the Go programming language. It integrates well with Go’s built-in testingpackage, but can be used in other contexts too.

Mocks will enable to write complex functional test cases while mocking the interfaces. You can follow more information in here.

go install github.com/golang/mock/mockgen@v1.6.0

Setup Project

Now we understood the array of test and mock library. Let’s implement sample test case for integer average function.

First create folder hierarchy along with *.go files as below,

folder hierarchy
  • addition : include addition function
package addition

type Addition struct {
}

func (Addition) Add(a, b int) int {
return a + b
}
  • divide : includes divide function
package divide

type Divider struct {
}

func (Divider) Divide(a, b int) int {
return a / b
}
  • domain : includes interfaces ( better include below interfaces in two different files)

note : commented line can be used for generating mock files

package domian

//go:generate mockgen -source=addition.go -package=mocks -destination=../mocks/addition.go
type Additional interface {
Add(a, b int) int
}
package domian

//go:generate mockgen -source=divide.go -package=mocks -destination=../mocks/divide.go
type Divide interface {
Divide(a, b int) int
}
  • mocks : no need to write files that will be generated by go mock

Let’s create avarage.go file in project folder with below code,

package gomocktest

import (
domian "go-mock-test/domain"
)

type Mathematics struct {
Addition domian.Additional
Divide domian.Divide
}

func (m Mathematics) Avarage(avg int, value ...int) int {
var sum int
for _, t := range value {
sum = m.Addition.Add(sum, t)
}
return m.Divide.Divide(sum, avg)
}

you can find the code in here.

Write Test Cases

Test can be write in two ways. In here we are focusing on test case write with mock library.

Test function without mocking

Addition and divide struct are declare when initilizing mathematics struct. Average function is called as array of test cases,

func TestAvgInteger(t *testing.T) {
tests := map[string]struct {
avg int
b []int
wantError bool
expect int
}{
`correct integer avarage`: {
avg: 3,
b: []int{1, 2, 4},
expect: 2,
},
`incorrect integer avarage`: {
avg: 3,
b: []int{1, 2, 4},
expect: 1,
wantError: true,
},
}

mat := gomocktest.Mathematics{}
mat.Addition = addition.Addition{}
mat.Divide = divide.Divider{}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
out := mat.Avarage(test.avg, test.b...)
if (out != test.expect) != test.wantError {
t.Errorf("got :%d, expected :%d", out, test.expect)
}
})
}
}

Test function with mocking

Divide and addition interface can be mock why interfaces are bind as attribute to mathematic struct. Mock file can be generated via executing following command in project root directory,

mockgen -source=domain/divide.go -package=mocks -destination=mocks/divide.go
mockgen -source=domain/addition.go -package=mocks -destination=mocks/addition.go

You can control the expected input and output without considering inner layer.

func TestAvgIntegerMock(t *testing.T) {
tests := map[string]struct {
avg int
b []int
wantError bool
expect int
}{
`correct integer avarage`: {
avg: 3,
b: []int{1, 2, 4},
expect: 2,
},
`incorrect integer avarage`: {
avg: 3,
b: []int{3, 4, 6},
expect: 3,
wantError: true,
},
}

mat := gomocktest.Mathematics{}
ctrl := gomock.NewController(t)
mockAdd := mocks.NewMockAdditional(ctrl)
mockAdd.EXPECT().Add(gomock.Any(), gomock.Any()).Return(6).AnyTimes()

mockDivide := mocks.NewMockDivide(ctrl)
mockDivide.EXPECT().Divide(gomock.Any(), gomock.Any()).Return(2).AnyTimes()

mat.Addition = mockAdd
mat.Divide = mockDivide
for name, test := range tests {
t.Run(name, func(t *testing.T) {
out := mat.Avarage(test.avg, test.b...)
if (out != test.expect) != test.wantError {
t.Errorf("got :%d, expected :%d", out, test.expect)
}
})
}
}

Summary

This was only an overview for writing array of test cases. I’d recommended to use documentation of Testing. Understand notations, operators and underline architecture.

Thank you for reading this article. I hope you enjoyed it!

--

--