Original post

I have a small piece of code that goes communicates with GitHub API. It authenticates myself using my Token. It reads the token from my shell environment

export GHTOKEN=“foobar”

And then, after getting authenticated, it visits one my github repositories (specifically the one called “ghostfish”) and it fetches the description of it.

So I wanted to write some tests for it. That’s when all the confusion started … I didn’t want to spam github with my test, so I wanted to mock the function in a way so to avoid hitting the actual 3rd party service – that is github api.

What I did:

  • I converted my functions into methods
  • I created an interface that implements those methods
  • I used mockgen to generate the mock. Specifically the command was: mockgen -source=main. -destination=mocks/mock_githubClient. -package=$GOPACKAGE

Then, when I test for the coverage, I get 0%.

$ go tool cover -func cover.out                                                      
github.com/testf/main.go:24:    main            0.0%
github.com/testf/main.go:34:    Authenticated   0.0%
github.com/testf/main.go:44:    GetUserRepo     0.0%
total:                          (statements)    0.0%

How is that possible? I am confused …

Here’s the code, if you want to have a look:

main.go

package main

import (
        "context"
        "fmt"
        "log"
        "os"

        "github.com/google/go-github/v30/github"
        ".org/x/oauth2"
)

var (
        GithubClient githubClientInterface = &githubClient{}
)

type githubClientInterface interface {
        Authenticated(env string) *github.Client
        GetUserRepo(client *github.Client, user string) (*github.Repository, error)
}

type githubClient struct{}

func main() {
        client := GithubClient.Authenticated("GHTOKEN")
        repo, err := GithubClient.GetUserRepo(client, "drpaneas")
        if err != nil {
                fmt.Println("Error")
                log.Fatal(err)
        }
        fmt.Println(repo.GetDescription())
}

func (c *githubClient) Authenticated(env string) *github.Client {
        token := oauth2.Token{
                AccessToken: os.Getenv(env),
        }
        tokenSource := oauth2.StaticTokenSource(&token)
        tc := oauth2.NewClient(context.Background(), tokenSource)
        client := github.NewClient(tc)
        return client
}

func (c *githubClient) GetUserRepo(client *github.Client, user string) (*github.Repository, error) {
        repo, _, err := client.Repositories.Get(context.Background(), user, "ghostfish")
        if err != nil {
                return nil, err
        }
        return repo, err
}

main_test.go

package main

import (
        "fmt"
        "testing"

        "github.com/golang/mock/gomock"
        "github.com/google/go-github/v30/github"
        mocks "github.com/testf/mocks"
)

func TestWithGoMock(t *testing.T) {
        mockCtrl := gomock.NewController(t)
        defer mockCtrl.Finish()

        mockgithubClient := mocks.NewMockgithubClientInterface(mockCtrl)
        c := &github.Client{}
        mockgithubClient.EXPECT().Authenticated("whatever").AnyTimes().Return(c)
        client := mockgithubClient.Authenticated("whatever")

        s := "something"
        var x *string
        x = &s
        r := &github.Repository{Description: x}
        mockgithubClient.EXPECT().GetUserRepo(client, "drpaneas").AnyTimes().Return(r, nil)
        repo, _ := mockgithubClient.GetUserRepo(client, "drpaneas")
        output := fmt.Sprintf("%s", (repo.GetDescription()))
        if output != "something" {
                t.Error("It cannot fetch the repository name")
        }
}

mocks/mock_githubClient.go

// Code generated by MockGen. DO NOT EDIT.
// Source: main.go

// Package mock_main is a generated GoMock package.
package mock_main

import (
        gomock "github.com/golang/mock/gomock"
        github "github.com/google/go-github/v30/github"
        reflect "reflect"
)

// MockgithubClientInterface is a mock of githubClientInterface interface
type MockgithubClientInterface struct {
        ctrl     *gomock.Controller
        recorder *MockgithubClientInterfaceMockRecorder
}

// MockgithubClientInterfaceMockRecorder is the mock recorder for MockgithubClientInterface
type MockgithubClientInterfaceMockRecorder struct {
        mock *MockgithubClientInterface
}

// NewMockgithubClientInterface creates a new mock instance
func NewMockgithubClientInterface(ctrl *gomock.Controller) *MockgithubClientInterface {
        mock := &MockgithubClientInterface{ctrl: ctrl}
        mock.recorder = &MockgithubClientInterfaceMockRecorder{mock}
        return mock
}

// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockgithubClientInterface) EXPECT() *MockgithubClientInterfaceMockRecorder {
        return m.recorder
}

// Authenticated mocks base method
func (m *MockgithubClientInterface) Authenticated(env string) *github.Client {
        m.ctrl.T.Helper()
        ret := m.ctrl.Call(m, "Authenticated", env)
        ret0, _ := ret[0].(*github.Client)
        return ret0
}

// Authenticated indicates an expected call of Authenticated
func (mr *MockgithubClientInterfaceMockRecorder) Authenticated(env interface{}) *gomock.Call {
        mr.mock.ctrl.T.Helper()
        return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticated", reflect.TypeOf((*MockgithubClientInterface)(nil).Authenticated), env)
}

// GetUserRepo mocks base method
func (m *MockgithubClientInterface) GetUserRepo(client *github.Client, user string) (*github.Repository, error) {
        m.ctrl.T.Helper()
        ret := m.ctrl.Call(m, "GetUserRepo", client, user)
        ret0, _ := ret[0].(*github.Repository)
        ret1, _ := ret[1].(error)
        return ret0, ret1
}

// GetUserRepo indicates an expected call of GetUserRepo
func (mr *MockgithubClientInterfaceMockRecorder) GetUserRepo(client, user interface{}) *gomock.Call {
        mr.mock.ctrl.T.Helper()
        return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserRepo", reflect.TypeOf((*MockgithubClientInterface)(nil).GetUserRepo), client, user)
}