Search code examples
unit-testinggo

Mocking Dependent Methods in Go Testing


Not sure how exactly we mock in this situation in Golang.

Situation: I have an service which has two methods and method2 depends upon the method1. When I was mocking the method2 using the mockery, the actual method1 was getting called. Ideally mock should get called.

Here is the sample I have been working out.

Main.go file

func main() {
    s := NewService()
    fmt.Println(s.MethodOne())
}

Here is the sample service file which is in the same package.

type Service interface {
    MethodOne() string
    MethodTwo() string
}

type ActualService struct{}

func NewService() Service {
    return &ActualService{}
}

func (s *ActualService) MethodOne() string {
    return "MethodOne called"
}

func (s *ActualService) MethodTwo() string {
    return s.MethodOne() + " then MethodTwo called"
}

Used below mockery command to generate the mocks under mocks\service.go mockery --dir . --name Service --output ./mocks --case underscore

Here is the final service_test.go file

func TestMethodTwo(t *testing.T) {
    actualService := NewService()
    mockService := &mocks.Service{}
    mockService.On("MethodOne").Return("MockedMethod called ").Once()
    //actual := mockService.MethodTwo()
    actual := actualService.MethodTwo()
    expected := "Mocked MethodOne called then MethodTwo called"
    assert.Equal(t, expected, actual)
}

If I run the service_test.go actual method is getting called.

Error:          Not equal: 
                            expected: "Mocked MethodOne called then MethodTwo called"
                            actual  : "MethodOne called then MethodTwo called"

If I were to call other service method instead of the method1, it works perfect (mocked method gets called). But if the method is within the same service, this situation happens.

Am I doing something wrong in the mockery unit tests? Any suggestions? Thanks in advance.


Solution

  • In your test, you call NewService which returns an instance of ActualService, masked behind the Service interface (you shouldn't do that most of the time, BTW). You then create a mocks.Service instance, and call MethodTwo on the instance of ActualService, somehow expecting that instance to be magically made aware of the mock you've got set up. That's never, ever going to work. If you need to mock something, you have to look at dependencies. I personally like using mockgen for my tests (github.com/golang/mock/mockgen). I'd set this up something like this:

    package foo
    
    type ActualService struct {
        dep Dependency
    }
    
    //go:generate go run github.com/golang/mock/mockgen -destination mocks/deps_mock.go -package mocks your.mod/path/to/pkg/foo Dependency
    type Dependency interface {
        MethodOne() string
    }
    
    func NewService(d Dependency) *ActualService {
        return &ActualService{
            dep: d,
        }
    }
    
    func (a *ActualService) MethodTwo() string {
        return a.dep.MethodOne() + " then method two called"
    }
    

    With this, you can just run go generate to have mockgen generate the mocks for your interface(s), and then write the following test in foo_test.go:

    package foo_test // add _test so you force yourself to only test exposed methods
    
    import (
        "testing"
    
        "your.mod/path/to/pkg/foo" // import the package you're testing, this test lives in foo_test
        "your.mod/path/to/pkg/foo/mocks" // import the mocks package
    
        "github.com/golang/mock/gomock"
        "github.com/stretchr/testify/require"
    )
    
    func TestMethods(t *testing.T) {
        ctrl := gomock.NewController(t)
        dep := mocks.NewMockDependency(ctrl)
        defer ctrl.Finish()
    
        svc := foo.NewService(dep) // pass in mock
        // set up the expected number of calls and return value
        dep.EXPECT().MethodOne().Times(1).Return("Mock method one called")
        expect := "Mock method one called then method two called"
        require.Equal(t, expect, svc.MethodTwo()) // check expected return value
    }
    

    The gomock controller will fail the test if an expected call to a method on a dependency is not made, and because we can control the behaviour of the dependency, we now have control over what data the code we're testing (ie MethodTwo) is receiving to work with.

    This is how golang's interfaces are best used: interfaces, as a rule of thumb, should be used when specifying function/method arguments. Functions, especially constructors, return types. It's not up to your package to decide what the interface is users of your package need/interact with. Your package exposes an API through the exported names, conversely, your package communicates what it depends on by defining clear interfaces. In this small example above, it's clear that the package foo has a constructor, which depends on any type that implements a MethodOne method that returns a string. In turn, the type returned by this package gives you a type with a function MethodTwo, which returns a string. That return value depends on the dependency, which gives you a way to mock it and test the behaviour of MethodTwo, where that dependency comes from, and how that is implemented is beyond the scope of what you're testing. It's up to whatever type implements the Dependency interface to implement that logic, and to test it.

    irrelevant side-note

    Nowadays, we tend to call everything a mock, but technically what you are looking for here is technically a fake or spy. The differences between a stub, fake, spy, and mock are mostly academic (most ppl will know what you mean), but I'm a bit of a pedant when it comes to this stuff :)