# Mocking Made Easy with Parsley

In version v0.9 of Parsley, a powerful new feature is introduced: the generate mocks CLI command. This command generates configurable mock implementations from your service interfaces, making testing faster and more efficient.

In this article, you learn how to use this feature to generate mock objects for interfaces, configure their behavior, and assert expectations for method calls in tests. It also showcases a real-world example of integrating these mocks into your Go test suites.

# Why Mocks?

Mock objects are an essential part of writing unit tests. They allow you to replace the real implementations of your services with objects that simulate their behavior, giving you more control over your test environment. With Parsley’s new generate mocks command, you can generate mocks that trace method calls, verify parameters, and count invocations without writing additional boilerplate code.

# Defining a Service Interface

Let's begin with a simple service interface, Greeter, which defines two methods: SayHello and SayNothing.

package features

//go:generate parsley-cli generate mocks

type Greeter interface {
    SayHello(name string) (string, error)
    SayNothing()
}

By adding the //go:generate directive at the top, we instruct Parsley’s CLI to generate a mock implementation for this interface.

# The Generated Mock Code

After running the parsley-cli generate mocks command, Parsley generates the following mock implementation code. A mock.g.go file is automatically created, and any manual changes will be overwritten if the command is rerun.

package features

import (
    "github.com/matzefriedrich/parsley/pkg/features"
)

type greeterMock struct {
    features.MockBase
    SayHelloFunc   SayHelloFunc
    SayNothingFunc SayNothingFunc
}

type SayHelloFunc func(name string) (string, error)
type SayNothingFunc func()

const (
    FunctionSayHello   = "SayHello"
    FunctionSayNothing = "SayNothing"
)

func (m *greeterMock) SayHello(name string) (string, error) {
    m.TraceMethodCall(FunctionSayHello, name)
    return m.SayHelloFunc(name)
}

func (m *greeterMock) SayNothing() {
    m.TraceMethodCall(FunctionSayNothing)
    m.SayNothingFunc()
}

var _ Greeter = (*greeterMock)(nil)

// NewGreeterMock Creates a new configurable greeterMock object.
func NewGreeterMock() *greeterMock {
    mock := &greeterMock{
        MockBase: features.NewMockBase(),
        SayHelloFunc: func(name string) (string, error) {
            var result0 string
            var result1 error
            return result0, result1
        },
        SayNothingFunc: func() {},
    }
    mock.AddFunction(FunctionSayHello, "SayHello(name string) (string, error)")
    mock.AddFunction(FunctionSayNothing, "SayNothing()")
    return mock
}

# Configuring Mocks in Tests

Once the mock has been generated, it can be used in your tests as a drop-in replacement for the actual service. You can configure its behavior by overriding the functions for each method in the interface.

Here’s an example of how to test the GreeterMock type by configuring the behavior of SayHello and using Parsley’s Verify assertion helpers:

package features

import (
    "fmt"
    "github.com/matzefriedrich/parsley/pkg/features"
    "github.com/stretchr/testify/assert"
    "testing"
)

func Test_GreeterMock_SayHello(t *testing.T) {
    // Arrange
    mock := NewGreeterMock()
    mock.SayHelloFunc = func(name string) (string, error) {
        return fmt.Sprintf("Hi, %s", name), nil
    }

    const expectedName = "John"

    // Act
    _, _ = mock.SayHello("Max")
    actual, err := mock.SayHello(expectedName)

    // Assert
    assert.NoError(t, err)
    assert.Equal(t, "Hi, John", actual)

    assert.True(t, mock.Verify(FunctionSayHello, features.TimesOnce(), features.Exact(expectedName)))
    assert.True(t, mock.Verify(FunctionSayHello, features.TimesNever(), features.Exact("Jane")))
    assert.True(t, mock.Verify(FunctionSayHello, features.TimesExactly(2)))
}

# Verifying Method Calls (and Arguments)

The generated mocks automatically trace method calls and allow you to verify how often methods were invoked and with which arguments. Parsley provides a set of powerful assertion helpers to verify method calls:

# Counter verification functions

  • TimesOnce: Verifies that a method was called exactly once.
  • TimesNever: Verifies that a method was never called.
  • TimesExactly: Verifies that a method was called exactly n times.

# Argument matching

  • Exact: Matches arguments exactly.
  • IsAny: Matches any given argument - use this as a placeholder.

For example, in the test case above:

  • The mock verifies that SayHello was called once with the exact argument "John".

  • The mock checks that SayHello was never called with the argument "Jane".

  • Lastly, it verifies that SayHello was called exactly twice in total.

Note: You can also provide custom TimesFunc and ArgMatch callbacks to evaluate counter values and method call parameters.

# Conclusion

With the new generate mocks command, Parsley makes it easy to create fully configurable and traceable mock objects for your services.

The mock generation feature is especially useful for writing tests, where you want to simulate different service behaviors, verify method calls, and ensure your components interact correctly with each other.

# Using Parsley’s generator commands without Runtime Dependency Injection

Parsley’s generator commands, like the generate mocks command, can be used independently of Parsley’s runtime dependency injection. This flexibility allows developers to leverage powerful code generation features, such as creating mock implementations for interfaces, without adopting Parsley’s full DI system.

For instance, you may prefer to manually wire your dependencies in a traditional Go setup while still using Parsley’s mock generation capabilities for testing. This makes Parsley a versatile tool that can be integrated into various workflows, whether or not you choose to use runtime DI for your projects.