go-ai-ready-boilerplate / rules
oniharnantyo/go-ai-ready-boilerplate/.cursor/rules/08-testing-standards.mdc
Mandatory testing framework: testify/suite + mockery-generated mocks
Cursor rule1 starsChanged 50 days ago
- Installs packages
---
description: "Mandatory testing framework: testify/suite + mockery-generated mocks"
globs:
alwaysApply: true
---
# Rule: Testing Standards
## Mandatory Testing Framework
**ALL tests MUST use:**
- **testify** for assertions (`require` in test bodies, `assert` only where meaningfully better)
- **testify/suite** for test organization and structure
- **mockery** for interface mocking (use mockery tool to generate mocks from interfaces)
**NEVER use:**
- Other testing frameworks (except where explicitly required)
- Hand-written mocks in new test files (use mockery-generated mocks)
- Other assertion libraries
**Mock Generation Requirements:**
- Use `mockery` tool to generate mocks from interfaces
- Generate mocks with: `mockery --all --output=./mocks`
- Mock files are generated in `./mocks/` directory
- Import generated mocks as `package mocks_<package>`
- Regenerate mocks when interfaces change
## Test Structure Standards
Every test file MUST follow this structure:
```go
package <package>
import (
"testing"
"github.com/stretchr/testify/suite"
// Other imports...
)
// <Feature>TestSuite tests the <feature> functionality.
type <Feature>TestSuite struct {
suite.Suite
// Add suite fields here
mockDependency *MockDependency
handler *Handler
echo *echo.Echo
}
func (s *<Feature>TestSuite) SetupTest() {
// Setup: Initialize mocks and test fixtures
}
func (s *<Feature>TestSuite) TearDownTest() {
// Teardown: Clean up resources
}
// Test cases follow naming: Test_<MethodName>_<Scenario>
func (s *<Feature>TestSuite) Test_MethodName_Scenario() {
// Arrange: Set up expectations
// Act: Call the method
// Assert: Verify results
}
func Test<Feature>TestSuite(t *testing.T) {
suite.Run(t, new(<Feature>TestSuite))
}
```
## Mock Standards
**Mockery-Generated Mock Usage:**
1. **Generate mocks with mockery** from interfaces
2. **Import generated mocks** from `mocks_<package>` package
3. **Use testify/mock methods** on generated mocks
4. **Set expectations before calling** methods
5. **Assert expectations after** test execution
```go
// Import generated mocks (generated by mockery tool)
import "github.com/project/internal/application/ports/mocks_application_ports_driven_repositories"
// Test Usage
func (s *FeatureTestSuite) Test_CreateTask_Success() {
// Create generated mock
repo := new(mocks_application_ports_driven_repositories.MockTaskRepository)
// Set up expectations
repo.On("Create", mock.Anything, mock.MatchedBy(func(task domain.Task) bool {
return task.Title == "Test Task"
})).Return(nil)
handler := NewHandler(repo)
task, err := handler.CreateTask(context.Background(), "test")
// Assert results
s.NoError(err)
s.Equal("Test Task", task.Title)
// Verify mock was called as expected
repo.AssertExpectations(s.T())
}
```
**Mockery Setup:**
```bash
# Install mockery
go install github.com/vektra/mockery/v2@latest
# Generate mocks from interfaces
mockery --all --output=./mocks --dir=./internal/application/ports/driven
# Or generate for specific package
mockery --name=TaskRepository --output=./mocks
```
**Generated Mock File Structure:**
```
internal/
application/
ports/
driven/
repositories/
task_repository.go # Interface definition
mocks_application_ports_driven_repositories/
MockTaskRepository.go # Generated by mockery
```
## Assertion Standards
**Use `require`** in test bodies for critical assertions:
- Use `require.NoError()` for error checks
- Use `require.Equal()` for critical value comparisons
- Use `require.True()`/`require.False()` for boolean checks
- Only use `assert` when test should continue after failure
## Test Naming Conventions
- **Suite**: `<Feature>TestSuite`
- **Test methods**: `Test_<MethodBeingTested>_<Scenario>`
- **File**: `<feature>_test.go` (next to the code being tested)
## Setup/Teardown Patterns
```go
func (s *FeatureTestSuite) SetupTest() {
// Initialize mocks
s.mockDependency = &MockDependency{}
// Create handler/subject under test
s.handler = NewHandler(s.mockDependency)
// Set up Echo for HTTP tests
s.echo = echo.New()
}
func (s *FeatureTestSuite) TearDownTest() {
// Clean up
s.echo = nil
s.handler = nil
}
```
## Test Organization
**Group related tests in suite methods:**
```go
func (s *FeatureTestSuite) Test_Create_Scenario() { }
func (s *FeatureTestSuite) Test_Create_AnotherScenario() { }
func (s *FeatureTestSuite) Test_Update_Scenario() { }
func (s *FeatureTestSuite) Test_Delete_Scenario() { }
```
## HTTP Handler Testing Patterns
**Standard HTTP handler test pattern:**
```go
func (s *HandlerTestSuite) Test_Create_Returns201() {
// Arrange
expected := domain.Task{ID: "1", Title: "Test"}
s.mockService.On("Create", mock.Anything, mock.Anything).Return(expected, nil)
// Act
req := httptest.NewRequest(http.MethodPost, "/resource", body)
rec := httptest.NewRecorder()
c := s.echo.NewContext(req, rec)
err := s.handler.Create(c)
// Assert
s.NoError(err)
s.Equal(http.StatusCreated, rec.Code)
s.mockService.AssertExpectations(s.T())
}
```
## UseCase Testing Patterns
**Standard use case test pattern with mockery:**
```go
import "github.com/project/internal/application/ports/mocks_application_ports_driven_repositories"
func (s *UseCaseTestSuite) Test_Execute_Success() {
// Arrange
repo := new(mocks_application_ports_driven_repositories.MockTaskRepository)
publisher := new(mocks_application_ports_driven_repositories.MockTaskEventPublisher)
usecase := NewCreateTaskUseCase(repo, publisher)
input := CreateTaskInput{Title: "Test"}
// Set up expectations
repo.On("Create", mock.Anything, mock.MatchedBy(func(task domain.Task) bool {
return task.Title == "Test"
})).Return(nil)
publisher.On("PublishTaskCreated", mock.Anything, mock.Anything).Return(nil)
// Act
result, err := usecase.Execute(context.Background(), input)
// Assert
s.NoError(err)
s.Equal("Test", result.Title)
// Verify expectations
repo.AssertExpectations(s.T())
publisher.AssertExpectations(s.T())
}
```
## Error Testing Standards
**Always test error paths:**
```go
func (s *FeatureTestSuite) Test_Method_ReturnsError() {
s.mockDependency.On("Method", mock.Anything).Return(errors.New("test error"))
err := s.handler.DoSomething()
s.Error(err)
s.Contains(err.Error(), "test error")
}
```
## Context and Cancellation Testing
**Test context behavior:**
```go
func (s *FeatureTestSuite) Test_Method_HandlesContextCancellation() {
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel immediately
err := s.handler.Method(ctx)
s.Error(err)
}
```
## Integration vs Unit Testing
**Unit tests** (most common):
- Mock all external dependencies
- Fast execution
- Test single behavior
**Integration tests** (rare):
- Use real implementations
- Test across boundaries
- Keep in separate files: `<feature>_integration_test.go`
## Test Coverage Requirements
- **New code**: MUST have tests before considered complete
- **Bug fixes**: MUST add regression test
- **Features**: Table-driven tests for normal/edge/error cases
- **HTTP handlers**: Test all status codes and error paths
- **Use cases**: Test success path, domain errors, and infrastructure errors
## Forbidden Testing Patterns
❌ **NEVER do this:**
```go
// Hand-written mocks (for new code)
type fakeDependency struct{}
func (f *fakeDependency) Method() error { return nil }
// Other frameworks
testing.T().Errorf("expected %s", got) // Use testify assertions
// Complex mock logic in tests
if condition {
mock.Return("value1")
} else {
mock.Return("value2")
}
```
✅ **ALWAYS do this:**
```go
// Use mockery-generated mocks
import "github.com/project/internal/ports/mocks_ports"
mockDep := new(mocks_ports.MockDependency)
mockDep.On("Method").Return("value")
// testify assertions
s.NoError(err)
s.Equal("expected", got)
s.True(condition)
// Use mock.MatchedBy for complex matching
mockDep.On("Method", mock.MatchedBy(func(arg string) bool {
return arg == "expected_value"
})).Return(nil)
```
## Test Performance Requirements
- Tests MUST run quickly (aim for <100ms per suite)
- Use `t.Parallel()` for independent test cases
- Avoid sleep/polling in tests
- Mock external dependencies
## Documentation in Tests
- **Test suite**: Document what functionality is being tested
- **Complex test cases**: Add comments explaining the scenario
- **Edge cases**: Document why the test is necessary
## Quality Checklist
Before considering a test complete:
- [ ] Uses testify/suite
- [ ] Uses testify/mock for interfaces
- [ ] Uses require for critical assertions
- [ ] Has proper SetupTest/TearDownTest
- [ ] Tests both success and failure paths
- [ ] Mocks all external dependencies
- [ ] Asserts on mock expectations
- [ ] Follows naming conventions
- [ ] Runs quickly (<100ms per suite)
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
Posts are public.Sign in to post
No one has posted yet. Be the first.

