Now that we are aware of the implementation details of our example project, we can go ahead and write a simple, mock-based unit test for it. Before we begin, we need to create a mock for the API interface. This can be achieved by invoking the mockgen tool with the following options:
$ mockgen \
-destination mock/dependency.go \
github.com/PacktPublishing/Hands-On-Software-Engineering-with-Golang/Chapter04/dependency \
API
The preceding command does the following:
- Creates a mock folder in the dependency package
- Generates a file called dependency.go with the appropriate code for mocking the API interface and places it in the mock folder
So far, so good. How can we use the mock in our tests though? The first thing we need to do is create a gomock controller and associate it with the testing.T instance that gets passed to our test function by the Go standard library. The controller instance defines a Finish method that our code must always run before returning from the test (for example, via a defer statement). This method checks the expectations that were registered on each mock object and automatically fails the test if they were not met. Here's what the preamble of our test function would look like:
// Create a controller to manage all our mock objects and make sure // that all expectations were met before completing the test ctrl := gomock.NewController(t) defer ctrl.Finish() // Obtain a mock instance that implements API and associate it with the controller. api := mock_dependency.NewMockAPI(ctrl)
The purpose of this particular unit test is to verify that a call to the AllDependencies method with a specific input yields an expected list of dependency IDs. As we saw in the previous section, the implementation of the AllDependencies method uses an externally-provided API instance to retrieve information about each dependency. Given that our test will inject a mocked API instance into the Collector dependency, our test code must declare the expected set of calls to the mock. Consider the following block of code:
gomock.InOrder( api.EXPECT(). ListDependencies("proj0").Return([]string{"proj1", "res1"}, nil), api.EXPECT(). DependencyType("proj1").Return(dependency.DepTypeProject, nil), api.EXPECT(). DependencyType("res1").Return(dependency.DepTypeResource, nil), api.EXPECT(). ListDependencies("proj1").Return([]string{"res1", "res2"}, nil), api.EXPECT(). DependencyType("res2").Return(dependency.DepTypeResource, nil), )
Under normal circumstances, gomock would just check that the method call expectations are met, regardless of the order that they were invoked in. However, if a test relies on a sequence of method calls being performed in a particular order, it can specify this to gomock by invoking the gomock.InOrder helper function with an ordered list of expectations as arguments. This particular pattern can be seen in the preceding code snippet.
With the mock expectations in place, we can complete our unit by introducing the necessary logic to wire everything together, invoke the AllDependencies method with the input (proj0) that our mock expects, and validate that the returned output matches a predefined value ("proj1", "res1", "res2"):
collector := dependency.NewCollector(api) depList, err := collector.AllDependencies("proj0") if err != nil { t.Fatal(err) } if exp := []string{"proj1", "res1", "res2"}; !reflect.DeepEqual(depList, exp) { t.Fatalf("expected dependency list to be:\n%v\ngot:\n%v", exp, depList) }
This concludes our short example about using gomock to accelerate the authoring of mock-based tests. As a fun learning activity, you can experiment with changing the expected output for the preceding test so that the test fails. Then, you can work backward and try to figure out how to tweak the mock expectations to make the test pass again.