Go-github: Mocking go-github

Created on 4 Jun 2014  路  15Comments  路  Source: google/go-github

I would like to write tests for an app that uses a lot go-github.

For instance I would like to mock some methods in the client.*Services structs. Is this possible with the current usage of structs ? Wouldn't it be preferable that the client struct holds some interfaces rather than some structs ? With interfaces, mocking is very easy with struct embedding.

Most helpful comment

that would certainly work, and may end up being cleaner than what I was originally proposing, I'm not sure. I was thinking about defining interfaces for the individual services. So in your app...

type githubRepoService interface {
  ListReleases(...)
  // other repository methods used in your app
}

// other services used in your app

You don't need to provide a concrete implementation of these interfaces because go-github already does. You would however need to wrap them in a client that uses the interfaces...

type GitHubClient struct {
  Repositories  githubRepoService
  // optionally store and export the underlying *github.Client 
  // if you want easy access to client.Rate or other fields
}

func NewClient(httpClient *http.Client) GitHubClient {
  client := github.NewClient(httpClient)
  // optionally set client.BaseURL, client.UserAgent, etc

  return GitHubClient{
    Repositories: client.Repositories
    // any other services your app uses
  }
}

Adding more methods for your application only requires adding them to the relevant service interface, no need for an implementation, since again, go-github itself is the implementation.

The calling style should be exactly the same as using go-github directly... client.Repositories.ListReleases(...). The only change would be calling this NewClient method, rather than the one in go-github.

All 15 comments

yeah, that would have made a lot of sense... unfortunately, I wasn't aware of gomock when I started. :frowning: I'm pretty sure that would be a backward-compatible change though, it would just be a decent amount of work.

I agree about backward compatibility. I'll make the change in my fork and I would show you when I'm satisfied with the result.

Not sure if you've started work on this, but I remember the other thing that bugged me about making these interfaces... the godocs for interfaces with lots of methods are pretty terrible, since they all get squished into a single pre block. That's not a great reason not to use them, but it will certainly have a non-trivial impact on the usability of the documentation.

No I have'nt started yet (some main job to do :)).
I had a look to the godocs for interfaces, and yes it's ugly... I agree it would damage the actual documentation. This is a problem.

There is an old bug [1] and a CL [2], maybe godoc for interface will be improved in go 1.3. Until that, I don't know what to do.

[1] https://code.google.com/p/go/issues/detail?id=5860
[2] https://codereview.appspot.com/12723043

So here's one approach we've used here at Google (not for this library, but same situation)... Define the interface in your own application that mirrors the concrete type, then build your application to rely on that interface. In production, you just provide the concrete type from go-github and it satisfies the interface; in test you use gomock or whatever to provide alternate implementations.

This is a little uglier with go-github because the implementations you'd want to mock are the individual Service types, not the client itself. The good news is that you only need to define interfaces for the services and methods that your application cares about.

I'm not sure if it's a good long-term solution for you (it certainly works better with simpler types), but might be worth trying on one or two methods as an experiment.

Wow thanks for the information !
So if I well understood, I should create an interface for the go-github methods I use in my app, and use it as a replacement of the github.client struct. The interface implementation in production would be a struct that contains the github.client and chains the method invocations to it.

Something like that

type GoGithub interface {
     ListRelease(...)
    // other methods used in my app
}

and the implementation

type GoGithubCli struct {
    client *github.client
}

func (g GoGithubCli) ListRelease(...) {
   g.client.ListRelease(...)
}

For the tests I just have to provide an other implementation. That's fine !

that would certainly work, and may end up being cleaner than what I was originally proposing, I'm not sure. I was thinking about defining interfaces for the individual services. So in your app...

type githubRepoService interface {
  ListReleases(...)
  // other repository methods used in your app
}

// other services used in your app

You don't need to provide a concrete implementation of these interfaces because go-github already does. You would however need to wrap them in a client that uses the interfaces...

type GitHubClient struct {
  Repositories  githubRepoService
  // optionally store and export the underlying *github.Client 
  // if you want easy access to client.Rate or other fields
}

func NewClient(httpClient *http.Client) GitHubClient {
  client := github.NewClient(httpClient)
  // optionally set client.BaseURL, client.UserAgent, etc

  return GitHubClient{
    Repositories: client.Repositories
    // any other services your app uses
  }
}

Adding more methods for your application only requires adding them to the relevant service interface, no need for an implementation, since again, go-github itself is the implementation.

The calling style should be exactly the same as using go-github directly... client.Repositories.ListReleases(...). The only change would be calling this NewClient method, rather than the one in go-github.

Thanks for the clarification, I see. Your method is less verbose, I don't have to code a production implementation of the interface.

I'm going to go ahead and close this out, as there's no work to be done. The above mocking strategy described in my last comment (where the application itself defines the interfaces which go-github implements) is used heavily inside Google, both for go-github and lots of other libraries, and it's worked really well for us.

Could you please provide an example of a projects or code that mocks go-github, or a similar library, in the manner that you suggest?

I've grepped trough all the projects that import go-github and not a single one has managed to mock this library. The best effort I've seen just used a httptest and checked that the appropriate paths were called.

Unfortunately, all of the examples I have are internal Google projects which aren't open source. But they use exactly the method described above together with gomock.

For anyone potentially looking for an example of how to use the above scheme in a project. Here is an example of this:

In code: https://github.com/gaia-pipeline/gaia/blob/master/pipeline/git.go#L168-L194
In test: https://github.com/gaia-pipeline/gaia/blob/master/pipeline/git_test.go#L252

@artem-sidorenko Oh excellent. Thanks for fixing that. :)

There is a more minimal example also given here that I found helpful: https://github.com/google/go-github/issues/1181#issuecomment-497893994

Was this page helpful?
0 / 5 - 0 ratings