Mock helper is a tool to help create readable and easy tests, creating a simple and idiomatic way to mock interfaces and structures.
Mock helper is based on the Mock package from stretchr and the Jest test framework from NodeJS.
It aims to combine stretchr's strategy to create mocks, with the readability from the tests created with the Jest framework.
- Setup
- How to Mock
- Features
To download mock-helper and add it to your project, just run:
$ go get github.com/delivery-much/mock-helper
As said before, mock helper gives you tools to help you create and test mocks in a easy and readable way. The implementation of the mock itself is up to you.
For instance, lets say you have a database interface:
type MyDBInterface struct {
GetUserCount(userID string) (int, error)
}
This interface has one method, that receives a string, and returns a user count and an error.
And you want to test this method:
func GetCount(db MyDBInterface, userID string) (int, error) {
c, err := db.GetUserCount(userID)
if err != nil {
return 0, err
}
return c, err
}
That uses your interface.
You could easily do that using mock helper!
Just create a mock implementation for your interface, like this:
import (
"github.com/delivery-much/mock-helper/mock"
)
type dbMock struct {
mock.Mock
}
// create a function to instantiate your mock
func NewDBMock() dbMock {
return dbMock{
mock.NewMock(),
}
}
func (m *InterfaceMock) GetUserCount(userID string) (c int, err error) {
// Register the method call with the correct parameters
m.RegisterMethodCall("GetUserCount", userID)
// Gets the method response that was specified on the tests, given the method name and the parameters.
res := m.GetMethodResponse("GetUserCount", userID)
if res.IsEmpty() {
// if no response was specified, returns the default values
return
}
// if a response was specified, gets the values from it
return res.GetInt(0), res.GetError(1)
}
And then test your function, like this:
func TestGetCount(t *testing.T) {
dbMock := NewDBMock()
// set the mock response
dbMock.Method("GetUserCount").SetResponse(5, nil)
userID := "mockUserID"
// call your function
c, err := GetCount(dbMock, userID)
// make your assertions
assert.Equal(t, 5, c)
dbMock.
AssertCalledOnce(t).
AssertCalledWith(t, userID)
// reset the mock
dbMock.Reset()
// set the mock response (this time with an error)
mockErr := errors.New("Mock error")
dbMock.Method("GetUserCount").SetResponse(5, mockErr)
// call your function
c, err = GetCount(dbMock, userID)
// make your assertions
assert.Equal(t, 0, c)
assert.Equal(t, mockErr, err)
dbMock.
AssertCalledOnce(t).
AssertCalledWith(t, userID)
}
This way you can easily mock your interfaces and assert that they where called the correct way, and their return value where used correctly.
In its root, the Mock library has a series of tools to help you mock your interfaces and create your assertions.
The NewMock function returns a new and empty Mock struct.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func NewMyMock() MyMock {
return MyMock{
mock.NewMock(),
}
}
The SetMethodResponse function sets the response that the mock will return when calling the method with the specified name.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
m.SetMethodResponse("MyMethod", 10, "something")
m.GetMethodResponse("MyMethod") // Should return [10, "something"]
}
Note: Calling m.SetMethodResponse("MyMethod", ...)
is directly equivalent of calling m.Method("MyMethod").SetResponse(...)
.
The GetMethodResponse function returns the response that was specified for the method with the given method name and the call arguments, if any response with specific arguments was specified.
This method will return a MethodResponse. This response will be empty if no response was specified for the method, or will contain the response values for that method.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
method1 := m.Method("MyMethod1")
method2 := m.Method("MyMethod2")
method1.SetResponse(10, "something")
method1.WithArgs("MY ARG!").Returns(20, "something else")
m.GetMethodResponse("MyMethod1") // Should return [10, "something"]
m.GetMethodResponse("MyMethod1", "MY ARG!") // Should return [20, "something else"]
m.GetMethodResponse("MyMethod2") // Should return []
}
Note: Calling m.GetMethodResponse("MyMethod")
is directly equivalent of calling m.Method("MyMethod").GetResponse()
.
The RegisterMethodCall function registers a mock method call.
It receives the method name and the args that where passed on that method call, and registers that information inside the mock to be used later.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func (m *MyMock) MyMockFunc(param1 string, param2 int) {
m.RegisterMethodCall("MyMockFunc", param1, param2)
// rest of the implementation goes here
}
Note: It's imperative that the RegisterMethodCall its used correctly, with the correct function name and the correct parameters, so that later, in the tests, the assertions can be made safely and avoid false negatives or positives.
Both the RegisterMethodCall
and GetMethodResponse
functions receive the same parameters, the method name, and the parameters received by the method.
The GetResponseAndRegister
function combines these methods in one.
With this function, you can simplify your mock implementation as such:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func (m *MyMock) MyMockFunc(param1 string, param2 int) error {
res := m.GetResponseAndRegister("MyMockFunc", param1, param2)
if res.IsEmpty() {
return
}
return res.GetError(0)
}
The GetCalls function returns the calls that where made on that mock. It returns a slice of MockCall, this slice will be empty if the mock had no calls, or it will contain the calls that where made on that mock.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m1 := MyMock{
mock.NewMock(),
}
m2 := MyMock{
mock.NewMock(),
}
m1.RegisterMethodCall("MyMockFunc", 10, "42")
m1.GetCalls() // Should return [{ "MyMockFunc", [10, "42"] }]
m2.GetCalls() // Should return []
}
The Called function checks whether the mock has been called at least once. It returns a boolean value indicating whether any method calls have been registered on the mock instance.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
m.Called() // Returns false, as no method calls have been registered
m.RegisterMethodCall("MyMethod", "param1", 42)
m.Called() // Returns true, since at least one method call has been registered
}
The CalledOnce function checks whether the mock has been called exactly once. It returns a boolean value indicating whether there is exactly one method call registered on the mock instance.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
m.CalledOnce() // Returns false, as no method calls have been registered
m.RegisterMethodCall("MyMethod", "param1", 42)
m.CalledOnce() // Returns true, since there is exactly one method call registered
m.RegisterMethodCall("MyMethod", "param1", 42)
m.CalledOnce() // Returns false, since there whas more than one method call registered
}
The CalledTimes function checks whether the mock has been called a specific number of times.
It takes an integer argument n
and returns a boolean value indicating whether the method calls count matches the provided number n
.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
m.CalledTimes(0) // Returns true, as no method calls have been registered
m.RegisterMethodCall("MyMethod", "param1", 42)
m.CalledTimes(1) // Returns true, since there is exactly one method call registered
m.CalledTimes(2) // Returns false, since there's only one method call
}
The CalledWith function checks whether the mock has been called at least once with specific arguments. It takes variadic arguments representing the expected arguments and returns a boolean value indicating whether there is a method call with (at least) those arguments.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
m.RegisterMethodCall("MyMethod", "param1", 42)
m.CalledWith("param1", 42) // Returns true, since there's a matching method call
m.CalledWith(42, "param1") // Returns true, since there's a matching method call
m.CalledWith("param1", 43) // Returns false, since there's no matching call
m.CalledWith("param1") // Returns true, since one of the method calls has the param "param1"
}
The CalledWithExactly function checks whether the mock has been called at least once with exactly matching arguments, in the same order. It takes variadic arguments representing the expected arguments and returns a boolean value indicating whether there is a method call with (exactly) those arguments.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
m.RegisterMethodCall("MyMethod", "param1", 42)
m.CalledWith("param1", 42) // Returns true, since there's a matching method call
m.CalledWith(42, "param1") // Returns false, since the method order is incorrect
m.CalledWith("param1", 43) // Returns false, since there's no matching call
m.CalledWith("param1") // Returns false, since there's no matching call
}
The Reset function clears all registered method calls and responses from the mock, effectively resetting it to an empty state.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
m.RegisterMethodCall("MyMethod", "param1", 42)
m.Called() // Returns true, since there's a registered method call
m.Reset()
m.Called() // Returns false, as all method calls have been cleared
}
The Method function returns a Method instance that can be used to filter the mock's use information for a specific method.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
myMethod := m.Method("MyMethod")
myMethod.SetResponse("mock response")
// Perform tests using 'myMethod'
}
The MockCall
structure represents a mock method call, including the method name and the arguments passed during the call.
It has the properties:
MethodName
(string): The name of the method that was called.Args
([]any): A slice containing the arguments passed to the method during the call.
The HasArgument method checks whether a specific argument exists within the list of arguments for a mock call.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
func main() {
call := mock.MockCall{
MethodName: "MyMethod",
Args: []interface{}{"param1", 42},
}
result1 := call.HasArgument("param1") // Returns true
result2 := call.HasArgument(42) // Returns true
result3 := call.HasArgument("foo") // Returns false
}
The Method type represents mock usage information that is filtered for a specific method.
The SetResponse function allows you to specify the response values that the mock method should return when it's called. The provided response values must match the method's response signature in type and order.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
myMethod := m.Method("MyMethod")
myMethod.SetResponse("mock response")
myMethod.GetResponse() // Should return "mock response"
}
The GetResponse function gets the specified response for the method.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
myMethod := m.Method("MyMethod")
myMethod.SetResponse("mock response")
response := myMethod.GetResponse() // Should return "mock response"
}
The GetCalls function returns the mock method calls that were made specifically for the filtered method. It returns a slice of MockCall
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
myMethod := m.Method("MyMethod")
myMethod.GetCalls() // Returns [], as no method calls have been registered
m.RegisterMethodCall("MyMethod", "param1", 42)
calls := myMethod.GetCalls() // Returns a slice containing calls for "MyMethod"
}
The Called function checks if the mock method was called at least once.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
myMethod := m.Method("MyMethod")
myMethod.Called() // Returns false, as no method calls have been registered
m.RegisterMethodCall("MyMethod", "param1", 42)
myMethod.Called() // Returns true, since "MyMethod" was called
}
The CalledOnce function checks if the mock method was called exactly once.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
myMethod := m.Method("MyMethod")
myMethod.CalledOnce() // Returns false, as no method calls have been registered
m.RegisterMethodCall("MyMethod", "param1", 42)
myMethod.CalledOnce() // Returns true, since "MyMethod" was called exactly once
m.RegisterMethodCall("MyMethod", "param1", 42)
myMethod.CalledOnce() // Returns false, since "MyMethod" was called more than once
}
The CalledTimes function checks whether the mock method has been called a specific number of times.
It takes an integer argument n
and returns a boolean value indicating whether the method calls count matches the provided number n
.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
m := MyMock{
mock.NewMock(),
}
myMethod := m.Method("MyMethod")
myMethod.CalledTimes(0) // Returns true, as no method calls have been registered
m.RegisterMethodCall("MyMethod", "param1", 42)
myMethod.CalledTimes(1) // Returns true, since "MyMethod" was called once
myMethod.CalledTimes(2) // Returns false, since "MyMethod" was called only once
}
The CalledWith function checks whether the mock method has been called at least once with specific arguments. It takes variadic arguments representing the expected arguments and returns a boolean value indicating whether there is a method call with (at least) those arguments.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
mock := MyMock{
mock.NewMock(),
}
m := mock.Method("MyMethod")
m.RegisterMethodCall("MyMethod", "param1", 42)
m.CalledWith("param1", 42) // Returns true, since there's a matching method call
m.CalledWith(42, "param1") // Returns true, since there's a matching method call
m.CalledWith("param1", 43) // Returns false, since there's no matching call
m.CalledWith("param1") // Returns true, since one of the method calls has the param "param1"
}
The CalledWithExactly function checks whether the mock method has been called at least once with exactly matching arguments, in the same order. It takes variadic arguments representing the expected arguments and returns a boolean value indicating whether there is a method call with (exactly) those arguments.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
mock := MyMock{
mock.NewMock(),
}
m := mock.Method("MyMethod")
m.RegisterMethodCall("MyMethod", "param1", 42)
m.CalledWith("param1", 42) // Returns true, since there's a matching method call
m.CalledWith(42, "param1") // Returns false, since the method order is incorrect
m.CalledWith("param1", 43) // Returns false, since there's no matching call
m.CalledWith("param1") // Returns false, since there's no matching call
}
The WithArgs combined with the Returns function inside a method allows the developer to specify a response for a specific set of arguments. When the specified mock method is called with those arguments, the mock will return that specific respose.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
type MyMock struct {
mock.Mock
}
func MyTest() {
mock := MyMock{
mock.NewMock(),
}
m := mock.Method("MyMethod")
m.WithArgs("param1", 42).Returns("my specified return!!")
m.SetResponse("a default response")
mock.MyMethod("param1", 42) // Returns "my specified return!!"
mock.MyMethod("some other param", 12) // Returns "a default response"
}
Note: For this function to work properly, you must specify the params when the mock is called, either via RegisterMethodCall or GetResponseAndRegister functions.
The MethodResponse
type represents a response that a mock method should return.
It provides methods for retrieving specific types of response values from the method response.
The IsEmpty
function it's a helper function to check if the method response is empty.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
func main() {
response := mock.MethodResponse{}
response.IsEmpty() // Returns true
response := mock.MethodResponse{42, "PARAM"}
response.IsEmpty() // Returns false
}
The Get function returns the response value specified in the method response at the given index. This function panics if no value is found on the specified index.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
func main() {
response := mock.MethodResponse{"value1", 42}
value := response.Get(0) // Returns "value1"
value2 := response.Get(1) // Returns 42
value2 := response.Get(3) // Panics!!
}
The GetString function returns a string value specified in the method response at the given index. It panics if no response value is found on the specified index or if the value type is not a string.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
func main() {
response := mock.MethodResponse{"hello", "world", 42}
value := response.GetString(0) // Returns "hello"
value2 := response.GetString(1) // Returns "world"
value2 := response.GetString(2) // Panics!!! (since 42 is not a string)
value2 := response.GetString(3) // Panics!!! (since there is no value on the index 3)
}
The GetInt function returns an integer value specified in the method response at the given index. It panics if no response value is found on the specified index or if the value type is not a integer.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
func main() {
response := mock.MethodResponse{42, "test"}
value := response.GetInt(0) // Returns 42
value2 := response.GetInt(1) // Panics!!! (since "test" is not a integer)
value2 := response.GetInt(2) // Panics!!! (since there is no value on the index 2)
}
You can also get integer values from specific byte sizes using the
GetInt8
,GetInt16
,GetInt32
andGetInt64
functions!
The GetFloat32 function returns a float32 value specified in the method response at the given index. It panics if no response value is found on the specified index or if the value type is not a integer.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
func main() {
response := mock.MethodResponse{float32(42.3), "test"}
value := response.GetFloat32(0) // Returns 42.3
value2 := response.GetFloat32(1) // Panics!!! (since "test" is not a float32)
value2 := response.GetFloat32(2) // Panics!!! (since there is no value on the index 2)
}
The GetFloat64 function returns a float64 value specified in the method response at the given index. It panics if no response value is found on the specified index or if the value type is not a integer.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
)
func main() {
response := mock.MethodResponse{float64(42.3), "test"}
value := response.GetFloat64(0) // Returns 42.3
value2 := response.GetFloat64(1) // Panics!!! (since "test" is not a float64)
value2 := response.GetFloat64(2) // Panics!!! (since there is no value on the index 2)
}
The GetError function returns an error value specified in the method response at the given index. It panics if no response value is found on the specified index or if the value type is not an error. A nil value is considered a valid error response.
Example usage:
import (
"github.com/delivery-much/mock-helper/mock"
"errors"
)
func main() {
response := mock.MethodResponse{errors.New("error 1"), nil, 42}
value := response.GetError(0) // Returns the error instance
value2 := response.GetError(1) // Returns nil (valid error response)
value2 := response.GetError(2) // Panics!!! (since 42 is not an error)
value2 := response.GetError(3) // Panics!!! (since there is no value on the index 3)
}
Both the mock and the method structs have the Assert
method, that allows the user to assert the mock usage.
The Assert
method returns struct that provides a series of functions that can be used to create test cases:
CalledWith
-> asserts that the mock or method was called with a specific set of params (se func CalledWith for more)CalledWithExactly
-> asserts that the mock or method was called with a exactly specific set of params (se func CalledWithExactly for more)Called
-> asserts that the mock or method was called at least once (se func Called for more)CalledOnce
-> asserts that the mock or method was called exactly once (se func CalledOnce for more)CalledTimes
-> asserts that the mock or method was called a specific number of times (se func CalledTimes for more)
Developers can also assert the negation of a clausule, using the Not
function before calling any of the listed functions above.
In addition, these assertions can be chained to make your tests more readable, using the And
function.
Ex.:
func TestMock(t *testing.T) {
myMock := NewMock()
... // make your test case
// make your mock assertions
myMock.
Assert(t).
CalledTimes(2).
And().
CalledWith("mock param").
And().Not().
CalledWith("invalid param").
// you can do the same with a specific method!
myMock.
Method("MySpecificMethod").
Assert(t).
CalledTimes(1).
And().
CalledWith("mock param")
}
When using these assertion functions, in case an assertion fail, the test will fail with a message specifying wath happened exactly.
Sometimes when using the CalledWith or the CalledWithExactly functions, it's a real pain to match certain types of values.
For instance, if you want to assert that a method was called with a date, or if you simply want to assert that the mock was called with any value, you can use argument matchers!
An argument matcher is a helper struct that allows the user to specify what the library should do when comparing the expected value to the value that was used in the mock call.
The MatchAny
it's an argument matcher provided by this library, that will allow the user to match any value.
For instance, let's say that you want to assert that your mock was called with exactly three parameters, but only the value of the first parameter is important to match.
You could do something like this:
func TestMock(t *testing.T) {
myMock := mock.NewMock()
... // make your test case
// make your mock assertions
myMock.
AssertCalledWithExactly(
t,
"mock param",
mock.MatchAny{},
mock.MatchAny{},
)
}
The MatchType
is an argument matcher provided by this library that allows the user to match values by their type.
For instance, let's say you want to assert that your mock was called with a date. It can be challenging to match the date exactly, especially because the parameter often becomes time-sensitive.
You could do something like this:
func TestMock(t *testing.T) {
myMock := mock.NewMock()
... // make your test case
// make your mock assertions
myMock.
AssertCalledWith(
t,
mock.MatchType[time.Time]{},
)
}
The MatchType
matcher receives a type param, that specifies what type that parameter should be.
Users can also create their custom argument matcher structs, as long as the struct implements the ArgumentMatcher
interface:
type ArgumentMatcher interface {
Match(arg any) bool
}
All they need to do is implement a struct that has a function Match
, which receives an argument of type any
and returns a bool
.
The function must return true
if the argument matches, or false
otherwise.
Then, just pass that struct to the assertion method, and you're good to Go!