Skip to content

Commit

Permalink
feat(test): add framework-agnostic mock context
Browse files Browse the repository at this point in the history
Add MockContext[B] to help users test their controllers without framework
dependencies. Includes realistic examples and documentation.

- Move testing docs from README.md to TESTING.md
- Add mock context with getters/setters
- Add example user controller tests
- Use fuego_test package for end-user perspective

Closes #349
  • Loading branch information
olisaagbafor committed Jan 13, 2025
1 parent dc235ba commit 82afb5a
Show file tree
Hide file tree
Showing 4 changed files with 357 additions and 105 deletions.
51 changes: 4 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,52 +361,9 @@ Go instead of Node, I'm happy to use it.

[MIT](./LICENSE.txt)

[gin-gonic-issue]: https://github.com/gin-gonic/gin/issues/155
[contributors-url]: https://github.com/go-fuego/fuego/graphs/contributors

## Testing with Mock Context

Fuego provides a framework-agnostic mock context for testing your controllers. This allows you to test your business logic without depending on specific web frameworks.
## Testing

### Basic Usage
For information about testing your Fuego applications, please see [TESTING.md](TESTING.md).

```go
func TestMyController(t *testing.T) {
// Create a mock context with your request body type
ctx := fuego.NewMockContext[UserRequest]()

// Set up test data
ctx.SetBody(UserRequest{
Name: "John Doe",
Email: "[email protected]",
})

// Add query parameters if needed
ctx.SetURLValues(url.Values{
"filter": []string{"active"},
})

// Add path parameters
ctx.SetPathParam("id", "123")

// Call your controller
result, err := MyController(ctx)

// Assert results
assert.NoError(t, err)
assert.Equal(t, expectedResult, result)
}
```

### Features

The mock context supports:

- Type-safe request bodies with generics
- URL query parameters
- Path parameters
- Headers
- Custom context values
- Request/Response objects

This makes it easy to test your controllers without worrying about HTTP mechanics or framework specifics.
[gin-gonic-issue]: https://github.com/gin-gonic/gin/issues/155
[contributors-url]: https://github.com/go-fuego/fuego/graphs/contributors
195 changes: 195 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Testing in Fuego

This guide explains how to effectively test your Fuego applications, with a focus on using the mock context for testing controllers.

## Mock Context

Fuego provides a framework-agnostic mock context that allows you to test your controllers without depending on specific web frameworks. This makes it easy to focus on testing your business logic rather than HTTP mechanics.

### Basic Usage

```go
func TestMyController(t *testing.T) {
// Create a mock context with your request body type
ctx := fuego.NewMockContext[UserRequest]()

// Set up test data
ctx.SetBody(UserRequest{
Name: "John Doe",
Email: "[email protected]",
})

// Add query parameters if needed
ctx.SetURLValues(url.Values{
"filter": []string{"active"},
})

// Add path parameters
ctx.SetPathParam("id", "123")

// Call your controller
result, err := MyController(ctx)

// Assert results
assert.NoError(t, err)
assert.Equal(t, expectedResult, result)
}
```

### Features

The mock context supports:

- Type-safe request bodies with generics
- URL query parameters
- Path parameters
- Headers
- Custom context values
- Request/Response objects

### Advanced Usage

#### Testing with Headers

```go
func TestControllerWithHeaders(t *testing.T) {
ctx := fuego.NewMockContext[EmptyBody]()
ctx.SetHeader("Authorization", "Bearer token123")
ctx.SetHeader("Content-Type", "application/json")

result, err := MyAuthenticatedController(ctx)
assert.NoError(t, err)
}
```

#### Testing with Custom Context Values

```go
func TestControllerWithContext(t *testing.T) {
ctx := fuego.NewMockContext[EmptyBody]()
customCtx := context.WithValue(context.Background(), "user_id", "123")
ctx.SetContext(customCtx)

result, err := MyContextAwareController(ctx)
assert.NoError(t, err)
}
```

#### Testing with Request/Response Objects

```go
func TestControllerWithRequestResponse(t *testing.T) {
ctx := fuego.NewMockContext[EmptyBody]()
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/test", nil)

ctx.SetResponse(w)
ctx.SetRequest(r)

result, err := MyController(ctx)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, w.Code)
}
```

### Best Practices

1. **Use Table-Driven Tests**

```go
func TestUserController(t *testing.T) {
tests := []struct {
name string
body UserRequest
want UserResponse
wantErr bool
}{
{
name: "valid user",
body: UserRequest{Name: "John", Email: "[email protected]"},
want: UserResponse{ID: "123", Name: "John"},
},
{
name: "invalid email",
body: UserRequest{Name: "John", Email: "invalid"},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := fuego.NewMockContext[UserRequest]()
ctx.SetBody(tt.body)

got, err := CreateUser(ctx)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
```

2. **Test Error Cases**

```go
func TestErrorHandling(t *testing.T) {
ctx := fuego.NewMockContext[UserRequest]()
ctx.SetBody(UserRequest{}) // Empty body should trigger validation error

_, err := CreateUser(ctx)
assert.Error(t, err)
assert.Contains(t, err.Error(), "validation failed")
}
```

3. **Test Validation Rules**

```go
func TestValidation(t *testing.T) {
ctx := fuego.NewMockContext[UserRequest]()
ctx.SetBody(UserRequest{
Name: "", // Required field
Email: "invalid-email", // Invalid format
})

_, err := CreateUser(ctx)
assert.Error(t, err)
}
```

4. **Test Middleware Integration**

```go
func TestWithMiddleware(t *testing.T) {
ctx := fuego.NewMockContext[EmptyBody]()
ctx.SetHeader("Authorization", "Bearer valid-token")

// Test that middleware allows the request
result, err := AuthMiddleware(MyProtectedController)(ctx)
assert.NoError(t, err)

// Test that middleware blocks unauthorized requests
ctx.SetHeader("Authorization", "invalid-token")
_, err = AuthMiddleware(MyProtectedController)(ctx)
assert.Error(t, err)
}
```

### Tips for Effective Testing

1. Keep tests focused on business logic
2. Use meaningful test names that describe the scenario
3. Test both success and failure cases
4. Use helper functions for common test setup
5. Test validation rules thoroughly
6. Mock external dependencies when needed
7. Use subtests for better organization
8. Test edge cases and boundary conditions

## Contributing

If you find any issues or have suggestions for improving the testing utilities, please open an issue or submit a pull request.
2 changes: 1 addition & 1 deletion mock_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,4 @@ func (m *MockContext[B]) Request() *http.Request {
// SetRequest sets the mock request
func (m *MockContext[B]) SetRequest(r *http.Request) {
m.request = r
}
}
Loading

0 comments on commit 82afb5a

Please sign in to comment.