Let's go through a comprehensive example that covers common features of the stretchr/testify
library and mockery
for mocking in Golang. This example will include testing with assertions, using the require
package for strict assertions, testing HTTP handlers, and mocking dependencies using mockery
.
Scenario
Imagine we have a service that fetches user information from an external API. We want to test:
- The service's functionality.
- Its integration with an external client.
- Mocking the external client.
Project Structure
/project
│
├── main.go
├── service.go
├── service_test.go
├── user_client.go
├── mocks/
│ └── UserClient.go (generated by mockery)
└── go.mod
Code Overview
-
user_client.go
This file defines an interface for interacting with an external user API.package project type User struct { ID int Name string } type UserClient interface { GetUserByID(id int) (*User, error) }
-
service.go
This file contains a service that uses the UserClient to fetch user details.package project import "fmt" type UserService struct { client UserClient } func NewUserService(client UserClient) *UserService { return &UserService{client: client} } func (s *UserService) GetUserDetails(id int) (string, error) { user, err := s.client.GetUserByID(id) if err != nil { return "", fmt.Errorf("failed to get user: %w", err) } return fmt.Sprintf("User: %s (ID: %d)", user.Name, user.ID), nil }
-
Generating Mocks with
mockery
You can generate mocks for theUserClient
usingmockery
:mockery --name=UserClient --output=./mocks
This will generate a mock in
mocks/UserClient.go
. -
service_test.go
Now, let's write a test for theUserService
usingtestify
assertions and themockery-generated
mock.package project_test import ( "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/mock" "project" "project/mocks" ) func TestUserService_GetUserDetails_Success(t *testing.T) { // Create a new mock client mockClient := new(mocks.UserClient) // Define what the mock should return when `GetUserByID` is called mockClient.On("GetUserByID", 1).Return(&project.User{ ID: 1, Name: "John Doe", }, nil) // Create the UserService with the mock client service := project.NewUserService(mockClient) // Test the GetUserDetails method result, err := service.GetUserDetails(1) // Use `require` for error checks require.NoError(t, err) require.NotEmpty(t, result) // Use `assert` for value checks assert.Equal(t, "User: John Doe (ID: 1)", result) // Ensure that the `GetUserByID` method was called exactly once mockClient.AssertExpectations(t) } func TestUserService_GetUserDetails_Error(t *testing.T) { // Create a new mock client mockClient := new(mocks.UserClient) // Define what the mock should return when `GetUserByID` is called with an error mockClient.On("GetUserByID", 2).Return(nil, errors.New("user not found")) // Create the UserService with the mock client service := project.NewUserService(mockClient) // Test the GetUserDetails method result, err := service.GetUserDetails(2) // Use `require` for error checks require.Error(t, err) assert.Contains(t, err.Error(), "user not found") // Ensure that the result is empty assert.Empty(t, result) // Ensure that the `GetUserByID` method was called exactly once mockClient.AssertExpectations(t) }
Key Points of This Example
- Assertions with
testify
:
assert
andrequire
packages are used for different types of checks.require
is used for checks that should fail the test immediately if they fail (e.g., checking fornil
errors).assert
is used for checks that can continue even if they fail (e.g., comparing values).
- Mocking with
mockery
:
mockery
generates a mock of theUserClient
interface.- In the test, the mock is configured with
.On()
to specify expected inputs and.Return()
to specify the outputs. AssertExpectations
verifies that the mocked method was called with the expected inputs.
- In the test, the mock is configured with
- Testing Error Handling:
- One test checks the successful scenario, while the other tests how the service handles an error from the
UserClient
.
This setup covers the basic functionality of stretchr/testify
for assertions and mocking with mockery
, providing a structured and maintainable approach to unit testing in Golang.