mockey

repository·main·Indexed 21 days ago

https://github.com/bytedance/mockey

A Go library for mocking functions, methods, and variables at runtime by rewriting function instructions. It allows direct replacement of dependencies to reduce the need for interfaces in unit tests. Supports Go 1.13+ on Mac OS, Linux, and Windows (AMD64, ARM64). Requires disabling inlining and compilation optimization via -gcflags="all=-N -l" to function correctly.

Tokens
14.4K
Snippets
49
Records
64
Agent score
71%

What's inside mockey

  1. Mockey Features Overview

    main

    Mockey provides a wide range of mocking capabilities for Go:

    Function and Method Mocking

    • Basic: Supports simple, generic, and variadic functions/methods (both value and pointer receivers). Supports hooks.
    • Automatic Cleanup: PatchConvey and PatchRun automatically release mocks after each test case.
    • Special Cases: GetMethod handles unexported types, unexported methods, and methods in nested structs.
    • Advanced:
      • Interface mocking (experimental)
      • Conditional mocking
      • Sequential returns
      • Decorator pattern (execute original function while mocking)
      • Goroutine filtering (include, exclude, or target specific goroutines)
      • Accessing the Mocker object for advanced usage (e.g., counting execution frequency)

    Variable Mocking

    • Supports mocking regular variables.
    • Supports mocking function variables.
  2. Manage mock lifecycle with PatchConvey and PatchRun

    main

    To avoid manual defer calls for unpatching mocks, use PatchConvey or PatchRun. Both tools automatically release mocks when the provided function completes. They both support nesting, where each layer only releases mocks created within its scope.

    Which one to use?

    • PatchConvey: Use this if you are using the goconvey framework for assertions and test organization. It follows goconvey execution order.
    • PatchRun: Use this for a lightweight alternative if you do not need goconvey integration.
    // Using PatchRun (Lightweight)
    PatchRun(func() {
        Mock(Foo).Return("MOCKED-1!").Build()
        // ... test logic
    })
    // Mock is automatically released here
    
    // Using PatchConvey (for goconvey users)
    PatchConvey("Test Description", t, func() {
        PatchConvey("Nested scope", func() {
            Mock(Foo).Return("MOCKED-1!").Build()
            // ... test logic
        })
        // Nested mock is automatically released here
    })
  3. Manage mock lifecycles with `PatchConvey` and `PatchRun`

    main

    To avoid manual defer mocker.UnPatch() calls, use lifecycle management tools that automatically release mocks when the scope ends.

    ToolBest Use Case
    PatchConveyWhen using the goconvey framework for assertions and test organization. Supports nested usage.
    PatchRunA lightweight alternative when you don't need goconvey integration.

    Both tools support nesting; each layer only releases the mocks created within its own scope.

    // PatchRun Example
    func TestXXX(t *testing.T) {
    	PatchRun(func() {
    		Mock(Foo).Return("MOCKED-1!").Build()
    		res := Foo("anything")
    		if res != "MOCKED-1!" { t.Errorf("expected MOCKED-1!, got %s", res) }
    	})
    
    	// Mock is automatically released here
    	res := Foo("anything")
    	if res != "ori:anything" { t.Errorf("expected ori:anything, got %s", res) }
    }
  4. Mock interface methods with experimental interface mocking

    main

    Starting from v1.4.4, Mockey supports mocking interface methods. This mocks the corresponding methods of all implementation types of that interface. This feature is experimental and resides in the github.com/bytedance/mockey/exp/iface package.

    To limit the scope of mocking to specific implementation types, use the SelectType(typeName) and SelectPkg(pkgName) selectors.

    package main
    
    import (
    	"bytes"
    	"fmt"
    	"io"
    	"net"
    	"os"
    
    	. "github.com/bytedance/mockey/exp/iface"
    )
    
    func main() {
    	// Mock the Read method of all Reader interface implementation types
    	Mock(io.Reader.Read).Return(1, io.EOF).Build()
    
    	reader1 := bytes.NewReader(nil)
    	reader2 := bytes.NewBufferString("")
    	reader3 := new(net.TCPConn)
    	reader4 := new(os.File)
    
    	fmt.Println(io.ReadAll(reader1)) // [0] <nil>, mocked
    	fmt.Println(io.ReadAll(reader2)) // [0] <nil>, mocked
    	fmt.Println(io.ReadAll(reader3)) // [0] <nil>, mocked
    	fmt.Println(io.ReadAll(reader4)) // [0] <nil>, mocked
    }
  5. How to use Mockey for simple function mocking

    main

    Mockey allows you to mock functions and methods by rewriting function instructions at runtime. This eliminates the need to refactor code to use interfaces for dependency injection.

    Important Requirement: You must disable inlining and compiler optimizations during compilation, otherwise the mock will not work.

    To mock a function so that it always returns a specific value, use the Mock().Return().Build() chain.

    package main
    
    import (
    	"fmt"
    	"math/rand"
    
    	. "github.com/bytedance/mockey"
    )
    
    func main() {
    	// mock `rand.Int` to always return 1
    	Mock(rand.Int).Return(1).Build() 
    	
    	fmt.Printf("rand.Int() always returns: %v\n", rand.Int())
    }
  6. Mock functions inside a package's `init()`

    main

    Since init() functions run before unit tests, standard mocking often fails. To mock a function called within an init() block:

    1. Create a new package (e.g., package d) with an init() function that performs the mock. Use an environment variable check (e.g., os.Getenv("ENV") == "CI") to ensure the mock only runs in test environments.
    2. In the package you are testing, find the .go file that is first in alphabetical order (dictionary order) and add an import for package d at the very top.
    3. Run your tests with the environment variable set (e.g., ENV=CI go test ./...).
  7. Mock interface types

    main

    There are three ways to mock interface types:

    Method 1: Use GetMethod (via instance) Use GetMethod(instance, "MethodName") to retrieve the method from a concrete instance and mock it.

    Method 2: Dummy Implementation Create a dummy struct that embeds the interface, mock the method on that dummy type, and then mock the constructor function to return the dummy instance.

    Method 3: Interface Mock (Recommended) Use the experimental exp/iface package to mock the interface directly across all implementations.

    // Method 1: GetMethod
    instance := NewFoo()
    Mock(GetMethod(instance, "Foo")).Return("MOCKED!").Build()
    
    // Method 2: Dummy Implementation
    type foo struct{FooI}
    Mock((*foo).Foo).Return("MOCKED!").Build()
    Mock(NewFoo).Return(new(foo)).Build()
  8. How PatchConvey, PatchRun, and UnPatchAll manage mock lifecycles

    main

    Mockey uses a stack-based approach to manage mock lifecycles through PatchConvey and PatchRun.

    1. Context Creation: When PatchConvey or PatchRun is called, a new layer (a map of mocks) is pushed onto a global stack (gMocker).
    2. Mock Registration: Any mock created via Mock(...).Build() within that context is added to the top-most layer of the stack.
    3. Isolation & Nesting: Nested calls to PatchConvey or PatchRun push new layers onto the stack. This allows inner tests to override outer mocks without affecting the outer scope's state once the inner test finishes.
    4. Automatic Cleanup: When a PatchConvey or PatchRun block exits, the top layer of the stack is popped, and all mocks in that layer are automatically unpatched.
    5. Manual Cleanup: UnPatchAll allows for explicit cleanup of the current scope's mocks.
  9. Call the original function from a mock using Origin()

    main

    If you want your mock hook to be able to call the original, un-mocked implementation, use the Origin(funcPtr interface{}) method. This is useful for partial mocking or wrapping the original behavior.

    Note: Origin only works when the origin hook is called directly. If the target function is called recursively, it will still hit the mock.

    // Example: Wrapping the original function
    var originalFun = func(p string) string { return p }
    
    // The mock calls the original with a modification
    mockFunc := func(p string) string {
        return originalFun(p + "mocked")
    }
    
    mockey.Mock(originalFun).To(mockFunc).Origin(&originalFun).Build()
  10. Troubleshoot: Common reasons why mocks fail

    main

    If your mocks are not taking effect, check the following:

    1. Optimizations: Ensure -gcflags="all=-N -l" is applied (see troubleshooting guide).
    2. Missing Build(): Ensure you called .Build() at the end of your mock chain. If the target function has no return value, you must still call an empty .Return() or use .To().
    3. Target Mismatch: The mock target must match exactly. For example, Mock((*A).Foo) is correct, but Mock(A{}.Foo) or Mock(a.Foo) where a is an instance will not work.
    4. Timing/Goroutines: If the function is called in another goroutine, it might execute after the mock has already been released (e.g., by PatchConvey).
    5. Execution Order: The function might be called during init() before the mock is set up. To mock functions in init(), create a separate package that performs the mock in its own init() and import it at the very top of your test files.
    6. Generics: Using non-generic mocks for generic functions may fail. Use GetMethod or the experimental interface mocking feature instead.