This is a creation in Article, where the information may have evolved or changed.
All along, I think it's very difficult to mock in go. Unlike a dynamic language or a language running on a VM, go requires that a mock be involved in the reservation when it is developed, otherwise it will not be allowed to enter the test. Development of the time need to have a headache, but also asked to consider the testability, really a little imposition. In addition, third-party libraries do not necessarily reserve space for mocks, which can only be despair detour. Many times, you cannot mock off some of the functions with side effects, you cannot overwrite the target path. Since the test does not have a critical path, then simply do not write the test. As a result, many of the go codes in the project have not been covered by tests in fact.
But lately I've found a library: Https://github.com/bouk/monkey
Seems like a farewell to the troubles of the beginning? Small area to experience the next, the feeling is very useful.
To make a long story short, themonkeylibrary replaces the actual execution address of the target function by modifying the memory address, and implements (almost) the mock of any function. You can specify the target function and then define an anonymous function to replace it. The replaced record will have a global table that can be restored to its original target function when it is not needed. Due to the use of black technology to modify the memory address, the authors suggest that you should never use it outside of the test environment. Linux and mac,windows that are currently supported only on the x86 architecture seem to have not been tested? In any case, supporting Linux and Macs is enough to cover the development and CI environments.
monkeyThe library is very simple to use, directly on the side of the sample code, the side explained:
package main
import (
"fmt"
"github.com/bouk/monkey"
"os"
"os/exec"
"reflect"
"testing"
)
// If we want to test the function call
func call(cmd string) (int, string) {
bytes, err := exec.Command("sh", "-c", cmd).CombinedOutput()
output := string(bytes)
if err != nil {
return 1, reportExecFailed(output)
}
return 0, output
}
// The above function will call it, this function must be mocked!
func reportExecFailed(msg string) string {
os.Exit(1) // Annoying side effects
return msg
}
func TestExecSussess(t *testing.T) {
// restore patch modification
// In actual use, UnpatchAll will be put into the teardown function
// However, this is handled in the testing that comes with go
defer monkey.UnpatchAll()
// Mock up the combined output method of *exec.Cmd returned by exec.Command
monkey.PatchInstanceMethod(
reflect.TypeOf((*exec.Cmd)(nil)),
"CombinedOutput", func(_ *exec.Cmd) ([]byte, error) {
return []byte("results"), nil
},
)
// mock out the reportExecFailed function
monkey.Patch(reportExecFailed, func(msg string) string {
return msg
})
rc, output := call("any")
if rc != 0 {
t.Fail()
}
if output != "results" {
t.Fail()
}
}
func TestExecFailed(t *testing.T) {
defer monkey.UnpatchAll()
// The last mock was the execution success, this time it was the execution failure
monkey.PatchInstanceMethod(
reflect.TypeOf((*exec.Cmd)(nil)),
"CombinedOutput", func(_ *exec.Cmd) ([]byte, error) {
return []byte(""), fmt.Errorf("sth bad happened")
},
)
monkey.Patch(reportExecFailed, func(msg string) string {
return msg
})
rc, output := call("any")
if rc != 1 {
t.Fail()
}
if output != "" {
t.Fail()
}
}
Executego test xx_test.go, you can run the above code.
A common requirement in the test is that a mock-off function is required at position A and that the original function needs to be called in position B to run. You need to usemonkeythe structure provided by the libraryPatchGuard. There is an example in the official documentation that is slightly tuned here:
package main
import (
"fmt"
"github.com/bouk/monkey"
"strings"
)
func main() {
var guard *monkey.PatchGuard
guard = monkey.Patch(fmt.Println, func(a ...interface{}) (n int, err error) {
s := make([]interface{}, len(a))
for i, v := range a {
s[i] = strings.Replace(fmt.Sprint(v), "hell", "*bleep*", -1)
}
// The following code is equivalent to
// guard.Unpatch()
// defer guard.Restore()
// return fmt.Println(s...)
guard.Unpatch()
n, err = fmt.Println(s...)
guard.Restore()
return
})
fmt.Println("what the hell?") // what the *bleep*?
fmt.Println("what the hell?") // what the *bleep*?
}
The key to the
code above is to call the original function before callingUnpatch, revert to the pre-mock condition, and then invokerestoreOnce the original function is called, and then re-play the mock. All that remains is to determine whether to run to position A or position B, depending on the input parameters.