import "github.com/Bayer-Group/mocka/v2"
Mocka is a simple mocking and stubbing library for the Go programming language. It is used to assist with writing unit tests around third-party functions.
All changes will be reflected in the CHANGELOG.
If you are looking to migrate from v1 to v2 check out the migration guide.
There are times when you would want to control the output of a third-party function in testing. Sometimes making a wrapper around that package/function is more effort than it is worth. Mocka is here to solve that problem. It allows you to control the output of functions without needing to write any additional code.
Currently if you would want to control the output of a function in go it would be akin to
// --- main.go ---
// alias function for unit testing
var jsonMarshal = json.Marshal
...
// --- main_test.go ---
// create temporary variable to store original function
var jsonMarshalOriginal func(v interface{}) ([]byte, error)
func TestMarshal(t *testing.T) {
jsonMarshalOriginal = jsonMarshal
jsonMarshal = func(v interface{}) ([]byte, error) {
return []byte("value"), nil
}
defer func() {
jsonMarshal = jsonMarshalOriginal
}()
// Your test code
}
This structure increases the length of unit tests; depending on how many functions are needing to control. Mocka provides a safe way to stub functions while also reducing the amount of code required.
Mocka does this safely using reflection, no calls to the
unsafe
package are made.
The mocka way would be
// --- main.go ---
// alias function for unit testing (in production code)
var jsonMarshal = json.Marshal
...
// --- main_test.go ---
func TestMarshal(t *testing.T) {
stub := mocka.Function(t, &jsonMarshal, []byte("value"), nil)
defer stub.Restore()
// Your test code
}
The
encoding/json
package was used in examples for simplicity and not for the need to control it's output.
There are some cases when interacting with a stub where errors can occur. Mocka uses a custom interface called TestReporter
, which is defined below, to fail tests for you.
type TestReporter interface {
Errorf(string, ...interface{})
}
TestReporter
is satisfied by the built-in testing.T
and other testing frameworks like Ginkgo by using GinkgoT()
.
func Function(
testReporter TestReporter,
functionPointer interface{},
returnValues ...interface{}) *Stub {
}
mocka.Function
replaces the provided function with a stubbed implementation. The Stub
has the ability to change the return values of the original function in many different cases. It also provides the ability to get metadata associated to any call against the original function.
After creating a Stub
it is recommended to defer
it's restoration. This is to ensure that the Stub
returns the original functionality back to the function. To restore a Stub
call the Restore
function.
Example
package main
import (
"testing"
"github.com/Bayer-Group/mocka/v2"
)
func TestMocka(t *testing.T) {
fn := func(str string) int {
return len(str)
}
stub := mocka.Function(t, &fn, 20)
defer stub.Restore()
actual := fn("1")
if actual != 20 {
t.Errorf("expected 20 but got %v", actual)
}
}
Mocka allows for the return values of a Stub
to be changed at any time and in many different cases. When creating Stub
it is required to specify a default set of return values it will return. If you want to change the default return values after the stub has been created simply call Return
on the Stub
.
Example
package main
import (
"testing"
"github.com/Bayer-Group/mocka/v2"
)
func TestMocka(t *testing.T) {
fn := func(str string) int {
return len(str)
}
stub := mocka.Function(t, &fn, 20)
defer stub.Restore()
if actual := fn("123"); actual != 20 {
t.Errorf("expected 20 but got %v", actual)
}
stub.Return(5)
if actual := fn("123"); actual != 5 {
t.Errorf("expected 5 but got %v", actual)
}
}
Mocka allows for return values to be changed based on how many times the original function has been called. To change the return values use the OnCall
method that can be used by either the Stub
or a custom set of arguments.
The callIndex uses zero-based indexing.
Mocka provides helper functions for accessing the first three times a function has been called. Instead of using the OnCall
method the following methods can be used OnFirstCall
, OnSecondCall
, or OnThirdCall
.
Example
package main
import (
"testing"
"github.com/Bayer-Group/mocka/v2"
)
func TestMocka(t *testing.T) {
fn := func(str string) int {
return len(str)
}
stub := mocka.Function(t, &fn, 20)
defer stub.Restore()
withArgs123 := stub.WithArgs("123")
withArgs123.OnCall(1).Return(5)
withArgs123.OnCall(3).Return(3)
if actual := fn("123"); actual != 20 {
t.Errorf("expected 20 but got %v", actual)
}
if actual := fn("123"); actual != 5 {
t.Errorf("expected 5 but got %v", actual)
}
if actual := fn("123"); actual != 20 {
t.Errorf("expected 20 but got %v", actual)
}
if actual := fn("123"); actual != 3 {
t.Errorf("expected 3 but got %v", actual)
}
}
Mocka allows for return values to be changed based on the arguments provided to the function. This can be done by using the WithArgs
method on the Stub
.
If
Return
is not called on theOnCallReturner
interface then it be ignored untilReturn
is called.
Example
package main
import (
"testing"
"github.com/Bayer-Group/mocka/v2"
)
func TestMocka(t *testing.T) {
fn := func(str []string, n int) int {
return len(str) + n
}
stub := mocka.Function(t, &fn, 20)
defer stub.Restore()
stub.WithArgs([]string{"123", "456"}, 2).Return(5)
fmt.Println(fn([]string{"123", "456"}, 2))
if actual := fn([]string{"123", "456"}, 2); actual != 5 {
t.Errorf("expected 5 but got %v", actual)
}
}
mocka accepts variadic arguments for WithArgs
the same as if you were calling the function itself.
You can still pass in custom matchers from the
match
package for each element in the variadic list.
Example
package main
import (
"testing"
"github.com/Bayer-Group/mocka/v2"
)
func TestMocka(t *testing.T) {
fn := func(str string, opts ...string) int {
return len(str) + len(opts)
}
stub := mocka.Function(t, &fn, 20)
stub.WithArgs("A", "B", "C").Return(5)
if actual := fn("A", "B", "C"); actual != 5 {
t.Errorf("expected 5 but got %v", actual)
}
if actual := fn("A"); actual != 20 {
t.Errorf("expected 20 but got %v", actual)
}
}
Similar to the Stub
the return values can be changed based on the call index of the original function for a specifc set of arguments. To change the return values for a specific call index use the OnCall
method.
mocka provides helper functions for accessing the first three times a function has been called. Instead of using the OnCall
method the following methods can be used OnFirstCall
, OnSecondCall
, or OnThirdCall
.
Example
package main
import (
"testing"
"github.com/Bayer-Group/mocka/v2"
)
func TestMocka(t *testing.T) {
fn := func(str string) int {
return len(str)
}
stub := mocka.Function(t, &fn, 20)
defer stub.Restore()
withArgs123 := stub.WithArgs("123")
withArgs123.OnCall(1).Return(5)
withArgs123.OnCall(3).Return(3)
if actual := fn("123"); actual != 20 {
t.Errorf("expected 20 but got %v", actual)
}
if actual := fn("123"); actual != 5 {
t.Errorf("expected 5 but got %v", actual)
}
if actual := fn("123"); actual != 20 {
t.Errorf("expected 20 but got %v", actual)
}
if actual := fn("123"); actual != 3 {
t.Errorf("expected 3 but got %v", actual)
}
}
mocka provides a powerful match
package that can be used in conjunction with the WithArgs
function. Sometimes you might not know the exact value a function is called with. This is a scenario where matchers can help navigate around that problem.
Currently there are over 25 built in matchers you can use. More information can be found at matcher descriptions.
The
match
package also provides the ability to create your own custom matchers.
Example
package main
import (
"testing"
"github.com/Bayer-Group/mocka/v2"
)
func TestMocka(t *testing.T) {
fn := func(str []string, n int) int {
return len(str) + n
}
stub := mocka.Function(t, &fn, 20)
defer stub.Restore()
stub.WithArgs(match.Anything(), 2).Return(10)
stub.WithArgs([]string{"123", "456"}, 2).Return(5)
if actual := fn([]string{"hello"}, 5); actual != 20 {
t.Errorf("expected 20 but got %v", actual)
}
if actual := fn([]string{"mocka"}, 2); actual != 10 {
t.Errorf("expected 10 but got %v", actual)
}
if actual := fn([]string{"123", "456"}, 2); actual != 5 {
t.Errorf("expected 5 but got %v", actual)
}
}
Setting the return values is only half of what mocka can do. Once a Stub
has been called you can retrieve the arguments and return values the original function was called with.
You can get how many times the original function was called after stubbing the function by using CallCount
.
mocka provides helper functions for checking if a Stub
has been called at least the first three times. Instead of using the CallCount
method the following methods can be used CalledOnce
, CalledTwice
, or CalledThrice
.
Example
package main
import (
"testing"
"github.com/Bayer-Group/mocka/v2"
)
func TestMocka(t *testing.T) {
fn := func(str string) int {
return len(str)
}
stub := mocka.Function(t, &fn, 20)
defer stub.Restore()
fn("first call")
fn("second call")
fn("third call")
if actual := stub.CallCount(); actual != 3 {
t.Errorf("expected 3 but got %v", actual)
}
}
GetCalls
returns all calls made to the original function that where captured by the stubbed implementation.
Example
package main
import (
"testing"
"github.com/Bayer-Group/mocka/v2"
)
type test struct {
arguments []interface{}
returnValues []interface{}
}
func TestMocka(t *testing.T) {
fn := func(str string) int {
return len(str)
}
stub := mocka.Function(t, &fn, 20)
defer stub.Restore()
fn("first call")
fn("second call")
fn("third call")
calls := stub.GetCalls()
if len(calls) != 3 {
t.Fatalf("expected 3 but got %v", actual)
}
tests := []test{
{arguments: []interface{}{"first call"}, returnValues: []interface{}{20}},
{arguments: []interface{}{"second call"}, returnValues: []interface{}{20}},
{arguments: []interface{}{"third call"}, returnValues: []interface{}{20}},
}
for i, tc := range tests {
call := calls[i]
if !reflect.DeepEqual(tc.arguments, call.Arguments()) {
t.Fatalf("expected arguments: %v, got: %v", tc.arguments, call.Arguments())
}
if !reflect.DeepEqual(tc.returnValues, call.ReturnValues()) {
t.Fatalf("expected return values: %v, got: %v", tc.returnValues, call.ReturnValues())
}
}
}
GetCall
returns the arguments and return values of the original function that was captured by the stubbed implementation. It will return these values for the specified time the function was called.
GetCall
will also panic if the call index is lower than zero or greater than the number of times the function was called.
The call index uses zero-based indexing
mocka provides helper functions for retrieving the arguments and return values for the first three calls. Instead of using the GetCall
method the following methods can be used GetFirstCall
, GetSecondCall
, or GetThirdCall
.
Example
package main
import (
"testing"
"github.com/Bayer-Group/mocka/v2"
)
func TestMocka(t *testing.T) {
fn := func(str string) int {
return len(str)
}
stub := mocka.Function(t, &fn, 20)
defer stub.Restore()
fn("first call")
fn("second call")
fn("third call")
call := stub.GetCall(2)
if !reflect.DeepEqual([]interface{}{"third call"}, call.Arguments()) {
t.Fatalf("expected arguments: %v, got: %v", []interface{}{"third call"}, call.Arguments())
}
if !reflect.DeepEqual([]interface{}{20}, call.ReturnValues()) {
t.Fatalf("expected return values: %v, got: %v", []interface{}{20}, call.ReturnValues())
}
}
In some special cases code will need to be run when the original function is called. This code is usually for performing side-effects. Mocka provides the ability to give a Stub
a function to be called when the original function is called. Call ExecOnCall
providing a function with the following signature func(arguments []interface{}) {}
to have it be called when the original function is called. This function will be called with the same arguments the original function is called with.
Example
package main
import (
"testing"
"github.com/Bayer-Group/mocka/v2"
)
func TestMocka(t *testing.T) {
fn := func(in <-chan int) <-chan int {
out := make(chan int, 1)
go func() {
out <- <-in
}()
return out
}
out := make(chan int, 1)
stub := mocka.Function(t, &fn, out)
defer stub.Restore()
stub.ExecOnCall(func(args []interface{}) {
c := args[0].(<-chan int)
out <- <-c
})
in := make(chan int, 1)
in <- 10
if actual := <-fn(in); actual != 10 {
t.Fatalf("expected: 10 got: %v", actual)
}
}
In many cases you might need to stub out many functions in a single test file. A Sandbox
allows you to simplify the restoration of many stubbed functions. You can create one Sandbox
where you can only call .Restore()
once for all stubbed functions.
To create a Sandbox
call mocka.CreateSandbox
passing in a test reporter. The test reporter will be used to fail any tests where a stubbing error occurs. All stubs created from the Sandbox
will use the same test reporter.
func Function(functionPointer interface{}, returnValues ...interface{}) {}
Sandbox.Function
behaves the same as mocka.Function
. It replaces the provided function with a stubbed implementation. The stub has the ability to change change the return values of the original function in many different cases. The stub also provides the ability to get metadata associated to any call against the original function.
func Sandbox.Restore() {}
Sandbox.Restore
will call .Restore()
on all stubs that have been created from the sandbox. Once the stubs have been restored they are removed from the sandbox. To ensure no other tests are effected by the stubs created from a Sandbox
, restore it after every test.
It is recommended to call Sandbox.Restore
in a defer directly after the sandboxes creation. If you are using a different testing package like Ginkgo then placing the restoration call in the AfterEach(func())
will work as well.
Example
package main
import (
"testing"
"github.com/Bayer-Group/mocka/v2"
)
func TestSandbox(t *testing.T) {
fn := func(str string) int {
return len(str)
}
sandbox := mocka.CreateSandbox(t)
defer sandbox.Restore()
sandbox.Function(&fn, 20)
actual := fn("1")
if actual != 20 {
t.Errorf("expected 20 but got %v", actual)
}
}