Testing Fuego Controllers
Fuego provides a MockContext
type that makes it easy to test your controllers without using httptest, allowing you to focus on your business logic instead of the HTTP layer.
Using MockContext
The MockContext
type implements the ContextWithBody
interface. Here's a simple example:
func TestMyController(t *testing.T) {
// Create a new mock context with the request body
ctx := fuego.NewMockContext(MyRequestType{
Name: "John",
Age: 30,
})
// Add query parameters
ctx.SetQueryParamInt("page", 1)
// Call your controller
response, err := MyController(ctx)
// Assert the results
assert.NoError(t, err)
assert.Equal(t, expectedResponse, response)
}
Complete Example
Here's a more complete example showing how to test a controller that uses request body, query parameters, and validation:
// UserSearchRequest represents the search criteria
type UserSearchRequest struct {
MinAge int `json:"minAge" validate:"gte=0,lte=150"`
MaxAge int `json:"maxAge" validate:"gte=0,lte=150"`
NameQuery string `json:"nameQuery" validate:"required"`
}
// SearchUsersController is our controller to test
func SearchUsersController(c fuego.ContextWithBody[UserSearchRequest]) (UserSearchResponse, error) {
body, err := c.Body()
if err != nil {
return UserSearchResponse{}, err
}
// Get pagination from query params
page := c.QueryParamInt("page")
if page < 1 {
page = 1
}
// Business logic validation
if body.MinAge > body.MaxAge {
return UserSearchResponse{}, errors.New("minAge cannot be greater than maxAge")
}
// ... rest of the controller logic
}
func TestSearchUsersController(t *testing.T) {
tests := []struct {
name string
body UserSearchRequest
setupContext func(*fuego.MockContext[UserSearchRequest])
expectedError string
expected UserSearchResponse
}{
{
name: "successful search",
body: UserSearchRequest{
MinAge: 20,
MaxAge: 35,
NameQuery: "John",
},
setupContext: func(ctx *fuego.MockContext[UserSearchRequest]) {
// Add query parameters with OpenAPI validation
ctx.WithQueryParamInt("page", 1,
fuego.ParamDescription("Page number"),
fuego.ParamDefault(1))
ctx.WithQueryParamInt("perPage", 20,
fuego.ParamDescription("Items per page"),
fuego.ParamDefault(20))
},
expected: UserSearchResponse{
// ... expected response
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create mock context with the test body
ctx := fuego.NewMockContext[UserSearchRequest](tt.body)
// Set up context with query parameters
if tt.setupContext != nil {
tt.setupContext(ctx)
}
// Call the controller
response, err := SearchUsersController(ctx)
// Check error cases
if tt.expectedError != "" {
assert.EqualError(t, err, tt.expectedError)
return
}
// Check success cases
assert.NoError(t, err)
assert.Equal(t, tt.expected, response)
})
}
}
Best Practices
- Test Edge Cases: Test both valid and invalid inputs, including validation errors.
- Use Table-Driven Tests: Structure your tests as a slice of test cases for better organization.
- Mock using interfaces: Use interfaces to mock dependencies and make your controllers testable.
- Test Business Logic: Focus on testing your business logic rather than the framework itself.
- Fuzz Testing: Use fuzz testing to automatically find edge cases that you might have missed.