Here's a good example of when using a mock (with testify/mock) has disadvantages compared to a stub:
Say you have an external API client in your code:
type API interface {
GetUser(id int) (User, error)
}
func GetUser(api API, id int) (User, error) {
return api.GetUser(id)
}
You want to test GetUser()
, but don't want to call the real API.
Using a mock:
mockAPI := new(mocks.API)
mockAPI.On("GetUser", 1).Return(User{ID: 1}, nil)
user, err := GetUser(mockAPI, 1)
assert.NoError(t, err)
assert.Equal(t, 1, user.ID)
This works, but has some downsides:
- You have to define the expected call on the mock using
On()
- If
GetUser()
is called with unexpected params, the test will fail - The mock logic can get complicated if there are many calls to verify
A stub would be simpler:
type StubAPI struct{}
func (s *StubAPI) GetUser(id int) (User, error) {
return User{ID: id}, nil
}
func TestGetUser(t *testing.T) {
api := new(StubAPI)
user, err := GetUser(api, 1)
assert.NoError(t, err)
assert.Equal(t, 1, user.ID)
}
Advantages of the stub:
- No need to define expected calls
- Will not fail if
GetUser()
is called with unexpected params - Simple to implement even if there are many calls to stub
Use stubs when:
- You want to provide pre-determined responses to method calls or simulate specific behavior of a dependency. Stubs are simple, lightweight, and focused on returning fixed data or behavior.
- You don't need to verify the interactions between the system under test and its dependencies. Stubs are not concerned with tracking method calls or validating the order and number of interactions.
- You want to make your tests more readable and easier to understand. Stubs make it clear what behavior is being tested, as the test setup explicitly defines the behavior of the dependency.
- You want to create less brittle tests. Since stubs don't enforce strict expectations on method calls, tests using stubs are less likely to break due to changes in the order or number of interactions with the dependency.
Use mocks when:
- You need to verify the interactions between the system under test and its dependencies. Mocks are designed to track method calls, their arguments, and the order in which they are called, allowing you to assert that the system under test is interacting with the dependency as expected.
- You want to enforce strict expectations on method calls. Mocks can be set up to expect specific method calls with specific arguments and in a specific order, ensuring that the system under test behaves as expected.
- You need to simulate complex behavior or state changes in the dependency. Mocks can be configured to return different responses based on different input arguments or to change their behavior over time, allowing you to test more complex scenarios.
- You are working with a large codebase or a team that follows strict testing practices. Mocks can help enforce a more rigorous testing approach, ensuring that all interactions between components are tested and verified.
In summary, choose stubs when you want a simple, lightweight, and less brittle testing approach focused on providing fixed responses or behavior. Choose mocks when you need to verify interactions between the system under test and its dependencies, enforce strict expectations, or simulate complex behavior. The choice between stubs and mocks depends on your specific testing requirements, the complexity of the system, and your team's testing practices.