Skip to content

Example code to demonstrate how to mock external clients via context.Context

Notifications You must be signed in to change notification settings

incident-io/golang-client-mocking

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mocking external client libraries using context.Context

This code is paired with a blog post:

Mocking external client libraries using context.Context

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:

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"))
    })
  })
})

Notes on Go codegen

This was originally in the blog post, but removed to keep it focused.

For those who are fresh to Go, it's worth explaining that as a language with a deliberate lack of meta-programming features, code generation is the de-facto way to approach several development problems.

This might feel unfamiliar or even awkward coming from other languages, and it certainly makes traversing diffs a bit harder.

When working with generated code, I'd give a few pieces of advice:

  • Add generated codepaths to your .gitattributes file as linguist-generated. While support is a bit flaky, this will cause GitHub to (mostly) hide generated code when looking at PRs, helping you to focus on the code real humans have written instead of computer generated noise.

  • Keep clear boundaries between human and computer generated code. In my example we've generated the client interface into client_interface.go, which will contain only computer generated code, but even that may be a bad idea- you'll have the fewest issues if you keep generated code in a separate package, as we've done with mock_slackclient.

  • Add CI steps to check generated code remains up-to-date, both to avoid human changes ending up alongside codegen, but also to catch any accidents where go generate was forgotten when checking in the code.

This repo demonstrates all of these, including the CI steps to keep generated code inline with the source.

About

Example code to demonstrate how to mock external clients via context.Context

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages