+1

GOLANG TESTING WITH STRETCHR/TESTIFY AND MOCKERY

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

  1. 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)
    }
    
  2. 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
    }
    
  3. Generating Mocks with mockery
    You can generate mocks for the UserClient using mockery:

    mockery --name=UserClient --output=./mocks
    

    This will generate a mock in mocks/UserClient.go.

  4. service_test.go
    Now, let's write a test for the UserService using testify assertions and the mockery-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

  1. Assertions with testify:
  • assert and require 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 for nil errors).
    • assert is used for checks that can continue even if they fail (e.g., comparing values).
  1. Mocking with mockery:
  • mockery generates a mock of the UserClient 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.
  1. 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.

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! 😃


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí