# Ginkgo Test Suites Structure and Organization
## Summary
This resource covers best practices for structuring and organizing Ginkgo test suites in Go applications. Ginkgo is a mature testing framework for Go designed to help you write expressive specs that works hand-in-hand with the Gomega matcher library. The guide focuses on how many test suite files to create, how to organize them by test type (unit, integration, contract, etc.), and effective patterns for managing complex test hierarchies.
## Test Suite Structure Philosophy
### One Suite Per Package Rule
When testing Go code, unit tests for a package typically reside within the same directory as the package and are named *_test.go. Ginkgo follows this convention. The general principle is:
**One Ginkgo suite per Go package being tested**
Each package should have:
1. **One suite bootstrap file**: `package_suite_test.go`
2. **Multiple spec files**: One or more `*_test.go` files containing the actual tests
3. **Shared test helpers**: Common setup and utility functions
### Basic Suite Structure
```text
mypackage/
├── handler.go # Production code
├── service.go # Production code
├── mypackage_suite_test.go # Suite bootstrap (TestMypackage function)
├── handler_test.go # Handler specs
├── service_test.go # Service specs
└── integration_test.go # Integration specs (optional)
```
## Suite Bootstrap File
Every Ginkgo suite needs exactly one bootstrap file generated with `ginkgo bootstrap`:
```go
// mypackage_suite_test.go
package mypackage_test
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"testing"
)
func TestMypackage(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Mypackage Suite")
}
```
**Key Points:**
- You should only ever call RunSpecs once and you can let Ginkgo worry about calling *testing.T for you
- The bootstrap file contains the entry point for the test suite
- Suite-level setup/teardown (`BeforeSuite`/`AfterSuite`) typically goes here
## Organizing Tests by Type
### Test Type Categories
Based on the test pyramid approach, organize your tests into these categories:
#### 1. Unit Tests (Foundation - Primary Focus)
- **Purpose**: Validate individual components in isolation with maximum execution speed and comprehensive coverage
- **Speed**: Fast (thousands of tests in under 60 seconds)
- **Testing Scope**: Business logic, component methods, data processing, input validation, edge cases
- **Dependencies**: External dependencies replaced with test doubles
- **File naming**: `*_test.go` in unit test directories
#### 2. Feature Tests (Complete User Workflow Validation)
- **Purpose**: Verify end-to-end user scenarios work correctly with minimal but comprehensive coverage
- **Speed**: Medium to slow
- **Testing Scope**: Critical user interaction paths, complete request/response cycles, authentication workflows
- **Dependencies**: Test from user perspective through actual interfaces
- **File naming**: `*_test.go` in feature test directories
#### 3. Integration Tests (Targeted Boundary Testing)
- **Purpose**: Validate complex integration points where unit and feature tests cannot provide adequate coverage
- **Speed**: Medium
- **When to Use**: Complex database operations, external API integration, file system operations, message queues
- **When NOT to Use**: Simple CRUD operations, core business logic, basic API calls
- **File naming**: `*_test.go` in integration test directories
#### 4. Contract Tests (Service Interface Agreements)
- **Purpose**: Ensure API contracts between services remain stable and functional across changes
- **Speed**: Fast to medium
- **When Essential**: Multi-service architectures, external API dependencies, team coordination
- **Implementation**: Consumer-driven contract testing using tools like Pact
- **File naming**: `*_test.go` in contract test directories
### Running Tests by Type
Use directory structure and labels to run specific test types:
```bash
# Rapid Feedback (Unit Tests) - < 2 minutes
ginkgo -r ./pkg/*/tests/unit/...
# Component Validation
ginkgo -r ./pkg/api/tests/...
# Integration Validation - < 10 minutes
ginkgo -r ./pkg/*/tests/integration/...
# System Confidence - < 30 minutes
ginkgo -r ./pkg/*/tests/feature/...
# Contract validation
ginkgo -r ./pkg/*/tests/contract/...
```
### Test Organization with Directory Structure and Labels
Organize tests primarily by directory structure, with labels for additional categorization:
```go
// In pkg/api/tests/unit/handlers/user_handler_test.go
var _ = Describe("User Handler", Label("unit", "handlers"), func() {
// Unit tests for user handler logic
})
// In pkg/api/tests/feature/user_registration_test.go
var _ = Describe("User Registration Flow", Label("feature", "critical-path"), func() {
// Complete user registration workflow tests
})
// In pkg/api/tests/integration/database_test.go
var _ = Describe("Database Repository", Label("integration", "database"), func() {
// Complex database operation tests
})
// In pkg/api/tests/contract/external_api_test.go
var _ = Describe("Payment Service Contract", Label("contract", "external-api"), func() {
// API contract validation tests
})
```
Run with directory structure and labels:
```bash
# Run by directory structure (primary method)
ginkgo -r ./pkg/*/tests/unit/...
ginkgo -r ./pkg/*/tests/feature/...
# Run with additional label filtering
ginkgo -r --label-filter="critical-path" ./pkg/*/tests/feature/...
ginkgo -r --label-filter="database" ./pkg/*/tests/integration/...
```
## Project Organization Patterns
### Single Component Projects
For applications with unified codebases:
```text
backend/tests/
├── unit/
│ ├── handlers/
│ ├── services/
│ ├── models/
│ └── utils/
├── feature/
│ ├── user_registration_flow_test.go
│ ├── content_processing_workflow_test.go
│ └── api_error_handling_test.go
├── integration/
│ ├── database_repository_test.go
│ ├── external_payment_api_test.go
│ └── file_upload_handler_test.go
├── contract/
│ ├── user_service_consumer_test.go
│ └── payment_service_provider_test.go
└── mocks/
└── # Test doubles and fixtures
```
### Multi-Component Projects (Recommended)
For complex applications with multiple independent components:
```text
backend/pkg/
├── api/
│ ├── api_suite_test.go # Bootstrap for API component
│ └── tests/
│ ├── unit/ # API handlers, business logic
│ ├── integration/ # External services, database repos
│ ├── feature/ # Complete API user journeys
│ ├── contract/ # API provider/consumer contracts
│ └── mocks/ # API-specific test doubles
├── content-pipeline/
│ ├── pipeline_suite_test.go # Bootstrap for pipeline component
│ └── tests/
│ ├── unit/ # Processing, transformations
│ ├── integration/ # File systems, queues, database repos
│ ├── feature/ # Complete pipeline workflows
│ └── mocks/ # Pipeline-specific test doubles
├── database/
│ ├── database_suite_test.go # Bootstrap for database component
│ └── tests/
│ ├── unit/ # Query builders, models, validation
│ ├── integration/ # Raw DB operations, migrations
│ └── mocks/ # Database-specific test doubles
└── shared/
├── shared_suite_test.go # Bootstrap for shared component
└── tests/
├── unit/ # Shared utility tests
└── mocks/ # Truly shared test doubles
```
## Advanced Suite Organization
### Hierarchical Test Structure
Ginkgo allows you to hierarchically organize the specs in your suite using container nodes:
```go
var _ = Describe("User Management System", func() {
Describe("User Model", Label("unit"), func() {
Context("Validation", func() {
It("should validate email format", func() {
// Test implementation
})
})
Context("Serialization", func() {
It("should marshal to JSON correctly", func() {
// Test implementation
})
})
})
Describe("User Service", Label("integration"), func() {
BeforeEach(func() {
// Setup database connection
})
Context("Creating Users", func() {
It("should create user in database", func() {
// Test implementation
})
})
})
})
```
### Shared Setup Patterns
#### Suite-Level Setup
```go
var (
dbClient *sql.DB
server *httptest.Server
)
var _ = BeforeSuite(func() {
// Setup shared resources
dbClient = setupTestDatabase()
server = setupTestServer()
DeferCleanup(func() {
dbClient.Close()
server.Close()
})
})
```
#### Test-Level Setup
```go
var _ = Describe("User Service", func() {
var (
userService *UserService
user *User
)
BeforeEach(func() {
// Fresh setup for each test
userService = NewUserService(dbClient)
user = &User{
Email: "
[email protected]",
Name: "Test User",
}
})
AfterEach(func() {
// Cleanup after each test
cleanupTestData(dbClient)
})
})
```
## Test Naming Conventions
### Naming Patterns for Different Test Types
Use prefixes to identify test types:
```go
// Unit tests
var _ = Describe("UserService", Label("unit"), func() {
It("should validate user email", func() {})
})
// Integration tests
var _ = Describe("UserAPI Integration", Label("integration"), func() {
It("should create user via API", func() {})
})
// Contract tests
var _ = Describe("External API Contract", Label("contract"), func() {
It("should match payment service schema", func() {})
})
```
### Alternative Naming with Test Functions
For traditional Go test compatibility:
```go
func TestUnit_UserValidation(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "User Validation Unit Tests")
}
func TestIntegration_UserAPI(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration tests in short mode")
}
RegisterFailHandler(Fail)
RunSpecs(t, "User API Integration Tests")
}
```
## Running Different Test Types
### Development Workflow Commands
**Rapid Feedback (Unit Tests) - Development:**
```bash
# Single component during development
ginkgo -r ./pkg/api/tests/unit/...
# Specific functionality
ginkgo -r ./pkg/api/tests/unit/handlers/...
# All unit tests across components
ginkgo -r ./pkg/*/tests/unit/...
# With parallelization for speed
ginkgo -r --procs=4 ./pkg/*/tests/unit/...
```
**Component Validation:**
```bash
# Complete component test suite
ginkgo -r ./pkg/api/tests/...
# All integration tests
ginkgo -r ./pkg/*/tests/integration/...
# All feature tests
ginkgo -r ./pkg/*/tests/feature/...
```
**Full System Validation:**
```bash
# Complete test suite
ginkgo -r ./pkg/*/tests/...
# With additional filtering
ginkgo -r --label-filter="critical-path" ./pkg/*/tests/...
```
### CI/CD Pipeline Structure
```yaml
# Example GitHub Actions workflow following test pyramid stages
# Stage 1: Rapid Feedback (< 2 minutes)
test-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
- run: |
ginkgo -r --procs=4 --race --cover ./pkg/*/tests/unit/...
# Static analysis and linting
go vet ./...
golangci-lint run
# Stage 2: Integration Validation (< 10 minutes)
test-integration:
runs-on: ubuntu-latest
needs: test-unit
services:
postgres:
image: postgres:13
redis:
image: redis:6
strategy:
matrix:
component: [api, content-pipeline, database]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
- run: |
# Run integration tests by component (parallel execution)
ginkgo -r ./pkg/${{ matrix.component }}/tests/integration/...
# Contract test validation
ginkgo -r ./pkg/${{ matrix.component }}/tests/contract/...
# Stage 3: System Confidence (< 30 minutes)
test-feature:
runs-on: ubuntu-latest
needs: test-integration
strategy:
matrix:
component: [api, content-pipeline]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
- run: |
# Feature tests by component
ginkgo -r ./pkg/${{ matrix.component }}/tests/feature/...
# Performance regression testing
go test -bench=. ./pkg/${{ matrix.component }}/...
```
## Best Practices
### Suite Organization
1. **One suite per package**: Keep test suites focused on a single package
2. **Clear naming**: Use descriptive names for test files and specs
3. **Logical grouping**: Use container nodes to help clarify the intent behind your suite and group related specs together
4. **Separate concerns**: Keep unit, integration, and contract tests clearly distinguished
### Test Independence
1. **No shared state**: Ginkgo assumes specs are independent
2. **Fresh setup**: Use `BeforeEach` for test-specific setup
3. **Proper cleanup**: Use `AfterEach` or `DeferCleanup` for teardown
4. **Randomization**: By default, Ginkgo will randomize the order in which the specs in a suite run
### Performance Optimization
1. **Parallel execution**: Running specs in parallel is as easy as ginkgo -p
2. **Suite-level setup**: Use `BeforeSuite` for expensive setup operations
3. **Test categorization**: Run fast tests frequently, slow tests less often
4. **Resource cleanup**: Ensure proper cleanup to prevent resource leaks
### Maintainability
1. **Descriptive specs**: Use clear and concise descriptions for Describe and It blocks to ensure the intent of the tests is easily understood
2. **Helper functions**: Extract common test operations into helper functions
3. **Table-driven tests**: Use Ginkgo's table specs for testing multiple scenarios
4. **Documentation**: Document complex test setups and special requirements
## Common Anti-Patterns to Avoid
### 1. Multiple Suites per Package
```go
// DON'T: Multiple bootstrap files in same package
func TestUserService(t *testing.T) { /* ... */ }
func TestUserRepository(t *testing.T) { /* ... */ }
```
### 2. Monolithic Test Files
```go
// DON'T: All tests in one massive file
// user_service_suite_test.go (5000+ lines)
```
### 3. Shared Mutable State
```go
// DON'T: Shared variables that can be mutated
var sharedUser = &User{} // This will cause test pollution
```
### 4. Test Dependencies
```go
// DON'T: Tests that depend on execution order
It("should create user", func() {
createdUser = createUser() // Setting global state
})
It("should find created user", func() {
Expect(findUser(createdUser.ID)).To(Succeed()) // Depends on previous test
})
```
## Related Resources
- [[Go API Testing Guide with Ginkgo + Gomega]] - Specific patterns for API testing
- [[The Test Pyramid]] - Understanding different test types and their purposes
- [Ginkgo Documentation](https://onsi.github.io/ginkgo/) - Official Ginkgo documentation
- [Gomega Documentation](https://onsi.github.io/gomega/) - Gomega matcher library