mock

package
v1.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 17, 2025 License: MIT Imports: 3 Imported by: 1

README

API Summary

This document describes Mock and Patch for functions. For variables and consts, see MOCK_VAR_CONST.md.

Mock exposes 3 Mock APIs to users:

  • Mock(fn, interceptor) - for 99% scenarios

  • MockByName(pkgPath, name, interceptor) - for unexported function

  • MockMethodByName(instance, name, interceptor) - for unexported method

And another 3 Patch APIs:

  • Patch(fn, replacer) - for 99% scenarios

  • PatchByName(pkgPath, name, replacer) - for unexported function

  • PatchMethodByName(instance, name, replacer) - for unexported method

Under 99% circumstances, developer should use Mock or Patch as long as possible because it does not involve hard coded name or package path.

The later two, MockByName and MockMethodByName are used where the target method cannot be accessed due to unexported, so they must be referenced by hard coded strings.

All 3 mock APIs take in an InterceptorFunc as the last argument, and returns a func(). The returned func() can be used to clear the passed in interceptor.

Difference between Mock and Patch

The difference lies at their last parameter:

  • Mock accepts an InterceptorFunc
  • Patch accepts a function with the same signature as the first parameter

Scope

Based on the timing when Mock*,or Patch* is called, the interceptor has different behaviors:

  • If called from init, then all goroutines will be mocked,
  • Otherwise, Mock* or Patch* is called after init, then the mock interceptor will only be effective for current gorotuine, other goroutines are not affected.

Interceptor

Signature: type InterceptorFunc func(ctx context.Context, fn *core.FuncInfo, args core.Object, results core.Object) error

  • If the interceptor returns nil, then the target function is mocked,
  • If the interceptor returns mock.ErrCallOld, then the target function is called again,
  • Otherwise, the interceptor returns a non-nil error, that will be set to the function's return error.

Mock

Signature: Mock(fn interface{}, interceptor InterceptorFunc) func()

Example:

package demo

import (
	"context"
	"testing"

	"github.com/xhd2015/xgo/runtime/core"
	"github.com/xhd2015/xgo/runtime/mock"
)

func MyFunc() string {
	return "my func"
}
func TestFuncMock(t *testing.T) {
	mock.Mock(MyFunc, func(ctx context.Context, fn *core.FuncInfo, args core.Object, results core.Object) error {
		results.GetFieldIndex(0).Set("mock func")
		return nil
	})
	text := MyFunc()
	if text != "mock func" {
		t.Fatalf("expect MyFunc() to be 'mock func', actual: %s", text)
	}
}

MockByName

Signature: MockByName(pkgPath string, name string, interceptor InterceptorFunc) func()

Example:

package demo

import (
	"context"
	"testing"

	"github.com/xhd2015/xgo/runtime/core"
	"github.com/xhd2015/xgo/runtime/mock"

	"github.com/my/another_pkg"
)

func TestGetNameShouldFail(t *testing.T) {
	mock.MockByName("github.com/my/another_pkg", "getNameThroughHTTP", func(ctx context.Context, fn *core.FuncInfo, args, results core.Object) error {
        results.FieldIndex(0).Set("failed")
		return nil
	})
    name := another_pkg.GetName()
    if name != "failed"{
        t.Fatalf("expect GetName()=%s, actual: %s","failed",name)
    }
}

Another package:

package another_pkg

func GetName() string {
    ...
    if notHitCache {
        name = getNameThroughHTTP()
        setCache(name)
    }
    return name
}

func getNameThroughHTTP() string {
    ...
    http.Do(....)
    ...
    return "ok"
}

MockMethodByName

Signature: MockMethodByName(instance interface{}, name string, interceptor InterceptorFunc) func()

Example:


package demo

import (
	"context"
	"testing"

	"github.com/xhd2015/xgo/runtime/core"
	"github.com/xhd2015/xgo/runtime/mock"

	"github.com/my/another_pkg"
)

func TestGetNameShouldFail(t *testing.T) {
    client := another_pkg.GetClient()

	mock.MockMethodByName(client, "getNameThroughHTTP",func(ctx context.Context, fn *core.FuncInfo, args, results core.Object) error {
        results.FieldIndex(0).Set("failed")
		return nil
	})
    name := client.GetName()
    if name != "failed"{
        t.Fatalf("expect GetName()=%s, actual: %s","failed",name)
    }
}

Another package:

package another_pkg

type client struct {
    endpoint string
}

type Client interface {
    GetName() string
}

var defaultClient = &client{endpoint:"default"}

func GetClient() Client {
    return defaultClient
}

func (c *client) GetName() string {
    ...
    if notHitCache {
        name = c.getNameThroughHTTP()
        setCache(name)
    }
    return name
}

func (c *client) getNameThroughHTTP() string {
    ...
    http.Do(....)
    ...
    return "ok"
}

Mock Generic Function

In the following functions, ToString[int] gets mocked, while ToString[string] does not.

package mock_generic

import (
	"context"
	"fmt"
	"testing"

	"github.com/xhd2015/xgo/runtime/core"
	"github.com/xhd2015/xgo/runtime/mock"
)

func ToString[T any](v T) string {
	return fmt.Sprint(v)
}

func TestMockGenericFunc(t *testing.T) {
	mock.Mock(ToString[int], func(ctx context.Context, fn *core.FuncInfo, args, results core.Object) error {
		results.GetFieldIndex(0).Set("hello world")
		return nil
	})
	expect := "hello world"
	output := ToString[int](0)
	if expect != output {
		t.Fatalf("expect ToString[int](0) to be %s, actual: %s", expect, output)
	}

	// per t param
	expectStr := "my string"
	outputStr := ToString[string](expectStr)
	if expectStr != outputStr {
		t.Fatalf("expect ToString[string](%q) not affected, actual: %q", expectStr, outputStr)
	}
}

Patch

package patch_test

import (
	"testing"

	"github.com/xhd2015/xgo/runtime/mock"
)

func greet(s string) string {
	return "hello " + s
}

func TestPatchFunc(t *testing.T) {
	mock.Patch(greet, func(s string) string {
		return "mock " + s
	})

	res := greet("world")
	if res != "mock world" {
		t.Fatalf("expect patched result to be %q, actual: %q", "mock world", res)
	}
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Mock added in v1.0.6

func Mock(fn interface{}, interceptor Interceptor) func()

Mock setup mock on given function `fn`. `fn` can be a function or a method, if `fn` is a method, only the bound instance will be mocked, other instances are not affected. The returned function can be used to cancel the passed interceptor.

func MockByName added in v1.0.9

func MockByName(pkgPath string, funcName string, interceptor Interceptor) func()

func MockMethodByName added in v1.0.9

func MockMethodByName(instance interface{}, method string, interceptor Interceptor) func()

func Patch added in v1.0.12

func Patch(fn interface{}, replacer interface{}) func()

Patch replaces `fn` with `replacer` in current goroutine. You do not have to manually clean up the replacer, as xgo will automatically clear the replacer when current gorotuine exits. However, if you want to clear the replacer earlier, this function returns a clean up function that can be used to clear the replacer.

func PatchByName added in v1.0.12

func PatchByName(pkgPath string, funcName string, replacer interface{}) func()

func PatchMethodByName added in v1.0.12

func PatchMethodByName(instance interface{}, method string, replacer interface{}) func()

Types

type Interceptor

type Interceptor func(ctx context.Context, fn *core.FuncInfo, args core.Object, results core.Object) error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL