Go Unit Tests
Generate comprehensive Go unit tests following testify patterns and the Arrange-Act-Assert methodology.
Planning Phase
Before writing tests, identify:
- •Test Structure: Determine if test suite (for structs with dependencies) or individual test functions (for standalone functions) should be used
- •Dependencies: Identify dependencies or side effects requiring mocks or stubs
- •Test Cases: Define scenarios covering happy paths, edge cases, and error conditions
- •Naming: Number each test case clearly (e.g.,
TestFunction_ValidInput_ReturnsExpectedResult,TestFunction_EmptyInput_ReturnsError)
Show the code without explanations during planning.
Implementation Patterns
Pattern 1: Test Suites for Structs with Dependencies
Use suite.Suite from testify for structs with dependencies.
Key Rules:
- •Create suite struct with
sut(System Under Test) field - •Implement
SetupTestmethod to initialize sut and dependencies - •Use constructor (typically
NewTypeName) to create instances - •Always use
_testsuffix for package name - •Use
suitemethods for assertions (e.g.,suite.Equal(v, 10)) - •Use
suite.Require()for error assertions (e.g.,suite.Require().ErrorIs,suite.Require().Error) - •Never use
.AssertExpectations(s.T())
Example:
go
package mypackage_test
import (
"testing"
"github.com/stretchr/testify/suite"
)
type MyStructTestSuite struct {
suite.Suite
sut *mypackage.MyStruct
}
func (s *MyStructTestSuite) SetupTest() {
// Initialize sut and dependencies
s.sut = mypackage.New()
}
func TestMyStructSuite(t *testing.T) {
suite.Run(t, new(MyStructTestSuite))
}
func (s *MyStructTestSuite) TestSomeMethod() {
// Arrange
input := "test input"
expected := "expected output"
// Act
result := s.sut.SomeMethod(input)
// Assert
s.Equal(expected, result)
}
func (s *MyStructTestSuite) TestSomeMethod_WithError() {
// Arrange
invalidInput := ""
// Act
result, err := s.sut.SomeMethodWithError(invalidInput)
// Assert
s.Require().Error(err)
s.Empty(result)
}
With Mocks:
go
package mypackage_test
import (
"context"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"github.com/example/project/test/mocks"
)
type UserServiceTestSuite struct {
suite.Suite
sut *mypackage.UserService
userRepoMock *mocks.MockUserRepository
tokenServiceMock *mocks.MockTokenService
}
func (s *UserServiceTestSuite) SetupTest() {
// Initialize mocks
s.userRepoMock = mocks.NewMockUserRepository(s.T())
s.tokenServiceMock = mocks.NewMockTokenService(s.T())
// Initialize sut with mocked dependencies
s.sut = mypackage.NewUserService(
s.userRepoMock,
s.tokenServiceMock,
)
}
func TestUserServiceSuite(t *testing.T) {
suite.Run(t, new(UserServiceTestSuite))
}
func (s *UserServiceTestSuite) TestCreateUser_ValidInput_CreatesUser() {
// Arrange
ctx := context.Background()
user := &mypackage.User{
Email: "test@example.com",
Name: "Test User",
}
s.userRepoMock.On("Create", mock.Anything, user).Return(nil)
// Act
err := s.sut.CreateUser(ctx, user)
// Assert
s.Require().NoError(err)
}
func (s *UserServiceTestSuite) TestCreateUser_RepositoryError_ReturnsError() {
// Arrange
ctx := context.Background()
user := &mypackage.User{
Email: "test@example.com",
Name: "Test User",
}
expectedError := errors.New("repository error")
s.userRepoMock.On("Create", mock.Anything, user).Return(expectedError)
// Act
err := s.sut.CreateUser(ctx, user)
// Assert
s.Require().ErrorIs(err, expectedError)
}
func (s *UserServiceTestSuite) TestGenerateToken_ValidUser_ReturnsToken() {
// Arrange
ctx := context.Background()
userID := "user-123"
expectedToken := "token-abc"
s.tokenServiceMock.On(
"Generate",
mock.Anything,
userID,
).Return(expectedToken, nil)
// Act
token, err := s.sut.GenerateToken(ctx, userID)
// Assert
s.Require().NoError(err)
s.Equal(expectedToken, token)
}
Mock Rules:
- •Always pass
mock.Anythingfor context parameters - •Mock naming follows pattern
MockType(e.g.,MockUserRepository,MockTokenService) - •Import mocks with aliases:
user_repository_mocks "github.com/project/internal/domain/repository/mocks"
Pattern 2: Tests for Standalone Functions
Use individual test functions with subtests for functions without instances.
Key Rules:
- •Create test functions using
func TestXxx(t *testing.T) - •Use
t.Runfor subtests covering different scenarios - •Use
requirefor error assertions (e.g.,require.ErrorIs,require.Error)
Example:
go
package mypackage_test
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestSomeFunction(t *testing.T) {
t.Run("valid input returns expected result", func(t *testing.T) {
// Arrange
input := "test input"
expected := "expected output"
// Act
result := SomeFunction(input)
// Assert
require.Equal(t, expected, result)
})
t.Run("empty input returns error", func(t *testing.T) {
// Arrange
input := ""
// Act
result, err := SomeFunctionWithError(input)
// Assert
require.Error(t, err)
require.Empty(t, result)
})
t.Run("nil input returns error", func(t *testing.T) {
// Arrange
var input *string
// Act
result, err := SomeFunctionWithPointer(input)
// Assert
require.ErrorIs(t, err, ErrNilInput)
require.Empty(t, result)
})
}
Test Structure Requirements
(CRITICAL) Arrange-Act-Assert Pattern
Every test must follow AAA pattern with explicit comments:
go
// Arrange // Act // Assert
Code Style
- •Never use inline struct construction; always create variable first
- •Maximum 120 characters per line
- •Test names must clearly indicate what is being tested
- •Add comments for complex test setups or assertions
Test Coverage
- •Include happy path scenarios
- •Include edge cases
- •Include error handling
- •Aim for minimum test scenarios possible while maintaining at least 80% coverage
Completion
When tests are complete, respond with: Tests Done, Oh Yeah!