A new way to mock go programs

Source: Internet
Author: User
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.


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.