For complex testing, the testify module
is a big life saver, it has 4 main packages: assert, require, mock and suite.
Their usages are quite straightforward from the testifyREADME file.
There is a series of tutorial
about how to use testify, pretty helpful.
Here in this blog I highlight the parts that are important to me.
Mock Objects Generation
Regarding mock, to create mocking objects for external dependencies, we use
mockery to
generate mock objects from go interface.
Thus to write testable code in Go, thinking about how to use interface
properly.
# Check current release tag and use it # The mockery binary will be downloaded to $(go env GOPATH)/bin go install github.com/vektra/mockery/v2@v2.20.0
Mock objects generation command:
1 2 3 4 5 6 7
# If you are using mockery binary downloaded, run it from the repo with absolute # path. # This will create a "mocks" folder in current directory and generate a mock # file named as HelloWorld.go.
# HelloWorld is the interface name inside student folder /<path to mockery parent folder>/mockery --dir student --name HelloWorld
type ExploreWorldSuite struct { suite.Suite // from mocks package hw *mocks.HelloWorld // from current package std *Student // other utilities ctx context.Context }
// Reset for every test case func(s *ExploreWorldSuite) SetupTest() { // The HelloWorld.go mock file contains NewHelloWorld func s.hw = mocks.NewHelloWorld(s.T()) s.ctx = context.TODO() std = &Student { Id: 1991, Name: "cheng", } }
// All methods that begin with "Test" are run as tests within a suite. func(s *ExploreWorldSuite) TestSayHello() { // mock Say method inside the HelloWorld interface s.hw.On("Say", mock.Anything, "World").Return(nil).Once()
// FirstTimeMeet calls Say method from HelloWorld interface got := s.std.FirstTimeMeet(s.ctx)
// There are lots more helper methods, use properly s.Require().True(got) // This can be helpful if the calling func does not have return or obvious // side effect s.hw.AssertExpectations(s.T()) }
// In order for 'go test' to run this suite, we need to create a normal test // function and pass our suite to suite.Run funcTestExploreWorldSuite(t *testing.T) { suite.Run(t, new(ExploreWorldSuite)) }
The commonly used assertions:
1 2 3 4 5 6 7 8
s.Require().Nil(err) s.Require().NotNil(err) // although not recommended to rely on error message s.Require().Contains(err.Error(), "xxxxxx") s.Require().EqualError(err, "xxxxxx")
# To run the whole package test suites go test -v -buildvcs=false -mod=readonly \ example.com/student
# To run specific suite in package go test -v -buildvcs=false -mod=readonly \ example.com/student \ -run <suite struct name regexp>
# to run test against the vendor folder # -mod=vendor go test -v -buildvcs=false -mod=vendor \ example.com/student \ -run <suite struct name regexp>
# to run specified tests in specific suite # -run and -testify.m have to be after package go test -v -buildvcs=false -mod=readonly \ example.com/student \ -run <suite struct name regexp> \ -testify.m <test name regexp>
# to run test without prior cache # -count 1 go test -v -buildvcs=false -mod=readonly \ -count 1 \ example.com/student \ -run <suite struct name regexp> \ -testify.m <test name regexp>