Mocking external client libraries using context.Context
This code is paired with a blog post:
The post describes a method for mocking external client libraries which can be applied to most codebases.
It mocks the client using the context parameter, ensuring all code will receive the same mock for the duration of an operation, and removing the need to explicitly stub a mock in code that might get called during a test.
We use ginkgo for our test suite, but the technique is independent of test framework.
To navigate:
- slackclient/client_test.go is an example test that confirms the mock works
- slackclient/client.go implements the client wrapper and some test helpers
- .circleci/config.yml contains the CI configuration, including things like checking generated code is up-to-date
- .gitattributes configures GitHub to collapse codegen artifacts in PRs by default
An exemplar test might look like this:
var _ = Describe("incident-io/codebase", func() {
var (
sc *mock_slackclient.MockSlackClient
)
slackclient.MockSlackClient(&ctx, &sc, nil)
Describe("some Slack things here", func() {
// Apply an expectation in the BeforeEach, before the test runs
BeforeEach(func() {
sc.EXPECT().GetConversationInfoContext(gomock.Any(), "CH123", false).
Return(&slack.Channel{
GroupConversation: slack.GroupConversation{
Conversation: slack.Conversation{
NameNormalized: "my-channel",
},
},
}, nil).Times(1)
})
Specify("returns a client that responds with the mock", func() {
client, _ := slackclient.ClientFor(ctx, "OR123")
channel, _ := client.GetConversationInfoContext(ctx, "CH123", false)
// We'll only receive this if the client generated by ClientFor is the mock we
// configured with a fake response in our BeforeEach.
Expect(channel.NameNormalized).To(Equal("my-channel"))
})
})
})