Golang Write Array of Unit Test Cases while Mocking the Interfaces
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 testing
package, 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,
- 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!