207 lines
4.5 KiB
Markdown
207 lines
4.5 KiB
Markdown
# Mocking and Test Fixtures
|
|
|
|
## Mocks with testify/mock
|
|
|
|
Create interfaces for your dependencies, then mock them.
|
|
|
|
> For the full testify/mock API (argument matchers, call modifiers, verification), see the `samber/cc-skills-golang@golang-stretchr-testify` skill.
|
|
|
|
```go
|
|
// Define the interface
|
|
type Database interface {
|
|
GetUser(id string) (*User, error)
|
|
CreateUser(user *User) error
|
|
}
|
|
|
|
// Mock implementation
|
|
type MockDatabase struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockDatabase) GetUser(id string) (*User, error) {
|
|
args := m.Called(id)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*User), args.Error(1)
|
|
}
|
|
|
|
func (m *MockDatabase) CreateUser(user *User) error {
|
|
args := m.Called(user)
|
|
return args.Error(0)
|
|
}
|
|
|
|
// Usage in tests
|
|
func TestService_GetUser(t *testing.T) {
|
|
is := assert.New(t)
|
|
|
|
mockDB := new(MockDatabase)
|
|
service := NewService(mockDB)
|
|
|
|
expectedUser := &User{ID: "1", Name: "John"}
|
|
mockDB.On("GetUser", "1").Return(expectedUser, nil)
|
|
|
|
user, err := service.GetUser("1")
|
|
|
|
is.NoError(err)
|
|
is.Equal(expectedUser, user)
|
|
mockDB.AssertExpectations(t)
|
|
}
|
|
|
|
func TestService_GetUser_NotFound(t *testing.T) {
|
|
is := assert.New(t)
|
|
|
|
mockDB := new(MockDatabase)
|
|
service := NewService(mockDB)
|
|
|
|
mockDB.On("GetUser", "999").Return(nil, ErrNotFound)
|
|
|
|
user, err := service.GetUser("999")
|
|
|
|
is.Error(err)
|
|
is.ErrorIs(err, ErrNotFound)
|
|
is.Nil(user)
|
|
mockDB.AssertExpectations(t)
|
|
}
|
|
```
|
|
|
|
## Mock Organization
|
|
|
|
For larger codebases, organize mocks alongside the code they mock:
|
|
|
|
```go
|
|
// user_service.go
|
|
type UserService struct {
|
|
db Database
|
|
email EmailService
|
|
}
|
|
type Database interface {
|
|
GetUser(id string) (*User, error)
|
|
CreateUser(user *User) error
|
|
}
|
|
type EmailService interface {
|
|
SendWelcomeEmail(to string) error
|
|
}
|
|
```
|
|
|
|
```go
|
|
// user_service_test.go
|
|
package mypackage_test
|
|
|
|
import (
|
|
"testing"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"path/to/mypackage"
|
|
)
|
|
|
|
// MockDatabase implements mypackage.Database
|
|
type MockDatabase struct {
|
|
mock.Mock
|
|
}
|
|
func (m *MockDatabase) GetUser(id string) (*mypackage.User, error) {
|
|
args := m.Called(id)
|
|
if args.Get(0) == nil { return nil, args.Error(1) }
|
|
return args.Get(0).(*mypackage.User), args.Error(1)
|
|
}
|
|
func (m *MockDatabase) CreateUser(user *mypackage.User) error {
|
|
return m.Called(user).Error(0)
|
|
}
|
|
|
|
// MockEmailService implements mypackage.EmailService
|
|
type MockEmailService struct {
|
|
mock.Mock
|
|
}
|
|
func (m *MockEmailService) SendWelcomeEmail(to string) error {
|
|
return m.Called(to).Error(0)
|
|
}
|
|
|
|
func TestUserService_CreateUser(t *testing.T) {
|
|
mockDB := new(MockDatabase)
|
|
mockEmail := new(MockEmailService)
|
|
service := mypackage.NewUserService(mockDB, mockEmail)
|
|
|
|
user := &mypackage.User{Name: "Test", Email: "test@example.com"}
|
|
mockDB.On("CreateUser", user).Return(nil)
|
|
mockEmail.On("SendWelcomeEmail", "test@example.com").Return(nil)
|
|
|
|
err := service.CreateUser(user)
|
|
|
|
assert.NoError(t, err)
|
|
mockDB.AssertExpectations(t)
|
|
mockEmail.AssertExpectations(t)
|
|
}
|
|
```
|
|
|
|
## Test Fixtures
|
|
|
|
Create reusable test data in a separate package or file:
|
|
|
|
```go
|
|
package fixtures
|
|
|
|
import "time"
|
|
|
|
var (
|
|
DefaultUser = &User{
|
|
ID: "user-123",
|
|
Name: "Jane Doe",
|
|
Email: "jane@example.com",
|
|
CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
}
|
|
|
|
AdminUser = &User{
|
|
ID: "admin-1",
|
|
Name: "Admin User",
|
|
Email: "admin@example.com",
|
|
Role: "admin",
|
|
CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
}
|
|
)
|
|
|
|
func NewUser(name, email string) *User {
|
|
return &User{
|
|
ID: "user-" + uuid.New().String(),
|
|
Name: name,
|
|
Email: email,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
}
|
|
```
|
|
|
|
## Time Mocking
|
|
|
|
Use `clockwork` to test time-dependent code without `time.Sleep()`:
|
|
|
|
```go
|
|
import (
|
|
"testing"
|
|
"time"
|
|
"github.com/jonboulle/clockwork"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestScheduler_AddJob(t *testing.T) {
|
|
is := assert.New(t)
|
|
|
|
fakeClock := clockwork.NewFakeClock()
|
|
scheduler := NewScheduler(fakeClock)
|
|
|
|
job := &Job{ID: "1", RunAt: time.Now().Add(1 * time.Hour)}
|
|
scheduler.AddJob(job)
|
|
|
|
is.Equal(1, scheduler.PendingCount())
|
|
|
|
// Advance fake time
|
|
fakeClock.Advance(2 * time.Hour)
|
|
|
|
is.Equal(0, scheduler.PendingCount())
|
|
}
|
|
```
|
|
|
|
Install clockwork:
|
|
|
|
```bash
|
|
go get github.com/jonboulle/clockwork
|
|
```
|