How to mock a method call of a struct in the test case


(Siddhanta Rath) #1

Here is the sample code for the struct and its method

type A struct {}

func (a *A) perfom(string){
...
...
..
} 

Then i want to call the method from the function invoke() present outside the package, sample code

var s := A{}
func invoke(url string){
out := s.perfom(url)
   ...
   ...
} 

Now, i want to write the test case for invoke function by mocking the perfom method of A.
In jave, we had mockito,jmock framework to stud method calls.
Is there any way in go, we can mock the method call of the struct with out introducing Interfaces in source code? or any good mocking framework in go is there ?


(Christoph Berger) #2

Hi @siddhanta_rath, I see these quick options:

  1. Change func invoke(url string) to

    func invoke(url string, f func(string))
    

    and call it as

    invoke(url, s.perform)
    

    In your test, write a mock function func mockPerform(s string) and pass it into invoke:

    invoke(url, mockPerform)
    

EDIT: The second option is plain wrong. See comment #6.

  1. Get rid of the global variable s. (This is a general advice - avoid global variables.) Change invoke to include the struct as a parameter:

    func invoke(url string, s A) {
        ...
    }
    ...
    s := A{}
    invoke(url, s)
    

    and then write a mock struct with mock functions and pass it to invoke:

    type mockA struct{}
    func (m *mockA) perform(s string) {
        ...
    }
    
    // in your test function:
    
    m := mockA{}
    invoke(url, m)
    

Both approaches need no interface and no mocking framework. The “secret” is to avoid hardcoded dependencies on entities outside the function you want to test. Pass them as parameters instead.


(Siddhanta Rath) #3

Thanks a ton :smile: @christophberger


(Christoph Berger) #4

You’re welcome! :slight_smile:


(Siddhanta Rath) #5

@christophberger I tried 2 solution, calling invoke from test method by passing mockA{} in my test function, It gave me compilation error cannot use m (type mockA) as type A in argument to invoke


(Christoph Berger) #6

You’re right, my bad. I wrote this code too quickly and did not test it. For the second option, you need an interface. Let me start from the beginning.

Your library package needs no change. Well, one change: Use a capital P for method Perform to make it visible outside the package.
For testing purposes, I also added a return parameter.

perf/perf.go:

package perf

type A struct{}

func (a *A) Perform(s string) int {
	return len(s)
}

In main, you define an interface:

main.go:

package main

import (
	"fmt"
	"perf" // adjust this path depending on where you put perf
)

type Performer interface {
	Perform(string) int
}

func invoke(url string, p Performer) int {
	return p.Perform(url)
}

func main() {
	a := perf.A{}
	url := "https://appliedgo.net" // shameless plug :-)
	fmt.Println(invoke(url, &a))  // We need to pass a pointer here because Perform() has a pointer receiver
}

And in your test file, create and use mockA. Invoke() will accept it as it implements the Performer interface:

invoke_test.go:

package main

import "testing"

type mockA struct{}

func (m *mockA) Perform(s string) int {
	return len(s)
}

func Test_invoke(t *testing.T) {
	m := mockA{}
	url := "https://appliedgo.net"
	want := 21
	if got := invoke(url, &m); got != want {
		t.Errorf("invoke() = %v, want %v", got, want)
	}
}

(This time I tested the code :slight_smile: )