minimock

repository·master·Indexed 20 days ago

https://github.com/gojuno/minimock

A Go library that generates statically typed mocks from interface declarations. It integrates with the standard 'testing' package and provides a builder pattern for expectation setup, including features like ExpectParams helpers, When/Then behaviors, and a Mock Controller to track expectations and support concurrent testing via Wait.

Tokens
4.1K
Snippets
16
Records
21
Agent score
73%

What's inside minimock

  1. Ensure mocks are being used

    master

    To prevent tests from passing when the code under test stops using its dependencies, use minimock.NewController(t) instead of passing *testing.T directly to mocks. The controller tracks all expectations and will fail the test if any expected method call was not performed.

    func TestSomething(t *testing.T) {
      // Using a controller ensures all expectations are met
      mc := minimock.NewController(t)
    
      formatterMock := NewFormatterMock(mc)
      formatterMock.FormatMock.Return("minimock")
    
      // If the code under test never calls formatterMock.Format(), 
      // the test will fail because of the controller.
    }
  2. Install the minimock CLI

    master

    To use minimock, you need to install its command-line tool. If you are using Go modules, you can install the latest version directly from source using go install.

    go install github.com/gojuno/minimock/v3/cmd/minimock@latest
  3. Manage mock lifecycles with Mocker and Controller

    master
    The Mocker interface is implemented by all mocks generated by minimock. It provides MinimockFinish() and MinimockWait(time.Duration). The Controller manages a collection of these mockers. When the controller's Finish() method is called (or when the test cleans up), it triggers MinimockFinish() on all registered mockers to ensure all expectations were met.
  4. Use the minimock CLI to generate mocks

    master

    The minimock CLI tool generates mock implementations for Go interfaces. You can specify specific interfaces, use wildcards to target all interfaces in a package, and control where the generated files are placed and how they are named.

    Basic Usage

    To generate mocks for all interfaces in the current directory, run:

    minimock

    To generate a mock for a specific interface (e.g., io.Writer) and place it in a specific package (e.g., ./buffer):

    minimock -i io.Writer -o ./buffer

    To generate mocks for multiple interfaces or entire packages using wildcards:

    # Generates mocks for fmt.Stringer and all interfaces in the 'io' package
    minimock -i fmt.Stringer,io.* -o ./buffer
    minimock -i io.Writer -o ./buffer
  5. Generate mocks using go:generate

    master

    You can integrate minimock into your Go workflow using the //go:generate directive. This allows you to regenerate mocks easily using go generate ./....

    By default, minimock inserts a standard //go:generate minimock ... line. If you want to ensure your project uses the specific version of minimock defined in your go.mod file, use the -gr flag:

    //go:generate go run github.com/gojuno/minimock/v3/cmd/minimock -i MyInterface -o ./mocks
  6. Mocking context with AnyContext

    master

    When a mocked method accepts a context.Context, you often don't care about the specific context instance. Use minimock.AnyContext to match any context argument in When or Expect calls.

    mc := minimock.NewController(t)
    
    // Using When with AnyContext
    senderMock := NewSenderMock(mc).SendMock.When(minimock.AnyContext, "message1").Then(nil)
    
    // Using Expect with AnyContext
    senderMock := NewSenderMock(mc).SendMock.Expect(minimock.AnyContext, "message").Return(nil)
  7. Control expected call counts with Times and Optional

    master

    You can control how many times a mocked method is expected to be called or whether it must be called at all.

    • .Times(n): Specifies that the method must be called exactly n times.
    • .Optional(): Disables the check that the method was called. Use this when a method might be called depending on the logic, but you want to provide a mock implementation if it is.
    // Expect exactly 10 calls
    mc := minimock.NewController(t)
    formatterMock := NewFormatterMock(mc).FormatMock.Times(10).Expect("hello %s!", "world").Return("hello world!")
    
    // Method might or might not be called
    formatterMock := NewFormatterMock(mc).FormatMock.Optional().Expect("hello %s!", "world").Return("hello world!")
  8. Set up mocks using When/Then helpers

    master

    The When(...).Then(...) pattern allows you to define multiple behaviors for the same method based on different input arguments. This is useful for simulating different scenarios (e.g., success vs. error) within the same test.

    mc := minimock.NewController(t)
    formatterMock := NewFormatterMock(mc)
    
    // Multiple behaviors for the same method
    formatterMock.FormatMock.When("Hello %s!", "world").Then("Hello world!")
    formatterMock.FormatMock.When("Hi %s!", "there").Then("Hi there!")
    
    // Or as a one-liner
    formatterMock = NewFormatterMock(mc).FormatMock.When("Hello %s!", "world").Then("Hello world!").FormatMock.When("Hi %s!", "there").Then("Hi there!")
  9. Set up mocks using the Set method

    master

    The .Set(func) method allows you to provide a custom implementation function for a mocked method. This is useful when you need dynamic logic or want to use invocation counters.

    // Provide a custom implementation
    mc := minimock.NewController(t)
    formatterMock := NewFormatterMock(mc).FormatMock.Set(func(string, ...interface{}) string {
      return "minimock"
    })
    
    // Use invocation counters for dynamic behavior
    formatterMock.FormatMock.Set(func(string, ...interface{}) string {
      return fmt.Sprintf("minimock: %d", formatterMock.BeforeFormatCounter())
    })
  10. Set up mocks using the Builder Pattern (Expect/Return)

    master

    The builder pattern allows you to set up expectations and return values for multiple methods in a single line. This is highly effective for table-driven tests.

    Use .Expect(args...) to define expected input arguments and .Return(results...) to define the output. You can also use .Inspect(func) to perform custom assertions on the arguments passed to the mock.

    // Basic Expect/Return
    mc := minimock.NewController(t)
    formatterMock := NewFormatterMock(mc).FormatMock.Expect("hello %s!", "world").Return("hello world!")
    
    // Multiple methods in one line
    readCloserMock := NewReadCloserMock(mc).ReadMock.Expect([]byte{1,2,3}).Return(3, nil).CloseMock.Return(nil)
    
    // Using Inspect for custom argument validation
    readCloserMock := NewReadCloserMock(mc).ReadMock.Inspect(func(p []byte){
      assert.Equal(mc, 2, p[1])
    }).Return(3, nil).CloseMock.Return(nil)
  11. Set up mocks using ExpectParams helpers

    master

    If an interface method has many arguments and you only want to validate one or two of them, minimock generates Expect[Type]Arg[N]Param helpers for each argument. This avoids the need to provide all arguments in an Expect call.

    // If the interface is: type If interface { Do(intArg int, stringArg string, floatArg float) }
    
    mc := minimock.NewController(t)
    ifMock := NewIfMock(mc).DoMock.ExpectIntArgParam1(10).ExpectFloatArgParam3(10.2).Return()
  12. Test concurrent code with mc.Wait

    master

    When testing code that runs in goroutines, the test might finish before the mocked methods are actually called. Use mc.Wait(duration) to block the test until all mocked methods have been called or the timeout is reached. If any expected calls are missing when Wait returns, the test fails.

    func TestSomething(t *testing.T) {
      mc := minimock.NewController(t)
    
      // Wait ensures all mocked methods are called within the timeout
      defer mc.Wait(time.Second)
    
      formatterMock := NewFormatterMock(mc)
      formatterMock.FormatMock.Return("minimock")
    
      // The method is called in a goroutine
      go formatterMock.Format("hello world!")
    }