Truong Phung
Posted on October 16, 2024
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.
-
-
Testing Error Handling:
- One test checks the successful scenario, while the other tests how the service handles an error from the
UserClient
.
- One test checks the successful scenario, while the other tests how the service handles an error from the
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.
If you found this helpful, let me know by leaving a ๐ or a comment!, or if you think this post could help someone, feel free to share it! Thank you very much! ๐
Posted on October 16, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.