package main

import (
    "fmt"
    "github.com/bouk/monkey"
    "os"
    "os/exec"
    "reflect"
    "testing"
)

// 假如我们要测试函数 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
}

// 上面的函数会调用它,这个函数一定要mock掉!
func reportExecFailed(msg string) string {
    os.Exit(1) // 讨人嫌的副作用
    return msg
}

func TestExecSussess(t *testing.T) {
    // 恢复 patch 修改
    // 实际使用中会把 UnpatchAll 放到 teardown 函数里
    // 不过在 go 自带的 testing 里就这么处理了
    defer monkey.UnpatchAll()
    // mock 掉 exec.Command 返回的 *exec.Cmd 的 CombinedOutput 方法
    monkey.PatchInstanceMethod(
        reflect.TypeOf((*exec.Cmd)(nil)),
        "CombinedOutput", func(_ *exec.Cmd) ([]byte, error) {
            return []byte("results"), nil
        },
    )
    // mock 掉 reportExecFailed 函数
    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()
    // 上次 mock 的是执行成功的情况,这一次轮到执行失败
    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()
    }
}

=====================================
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)
        }
        // 以下代码等价于
        // 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*?
}