This file covers repository-layer SQL mock tests. For the consolidated local dev and test guide (including all packages, integration tests, and troubleshooting), see docs/dev-test-guide.md.
This document describes the comprehensive repository unit tests implemented for the stellabill-backend project using SQL mocking to verify query correctness and error handling without external database dependencies.
The repository layer tests provide comprehensive coverage for:
- Plan repository operations
- Subscription repository operations
- Outbox repository operations
- Edge cases and error handling scenarios
- Concurrent access patterns
- Null value handling
- Retry logic and deadlock simulation
- github.com/DATA-DOG/go-sqlmock: SQL mocking framework for database operations
- github.com/stretchr/testify: Assertion and testing utilities
- github.com/google/uuid: UUID generation for test data
internal/
├── repositories/
│ ├── plans.go # Plan repository implementation
│ ├── plans_test.go # Plan repository unit tests
│ ├── subscriptions.go # Subscription repository implementation
│ ├── subscriptions_test.go # Subscription repository unit tests
│ └── edge_cases_test.go # Edge cases and error handling tests
└── outbox/
├── repository.go # Outbox repository implementation
└── repository_test.go # Outbox repository unit tests
Each repository follows a consistent testing pattern:
func TestRepository_Method(t *testing.T) {
db, mock, err := sqlmock.New()
require.NoError(t, err)
defer db.Close()
repo := NewRepository(db)
tests := []struct {
name string
input *InputType
expectedError string
setupMock func()
}{
// Test cases...
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setupMock()
err := repo.Method(tt.input)
if tt.expectedError != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedError)
} else {
assert.NoError(t, err)
}
assert.NoError(t, mock.ExpectationsWereMet())
})
}
}mock.ExpectQuery(`INSERT INTO plans`).
WithArgs(sqlmock.AnyArg(), "Plan Name", "29.99", "USD", "month", nil, "merchant-123", sqlmock.AnyArg(), sqlmock.AnyArg()).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uuid.New().String()))mock.ExpectQuery(`SELECT.*FROM plans WHERE id = \$1`).
WithArgs("nonexistent").
WillReturnError(sql.ErrNoRows)mock.ExpectExec(`UPDATE plans SET.*`).
WithArgs(...).
WillReturnError(fmt.Errorf("database connection failed"))Tests explicitly verify proper handling of nullable database columns:
// Test with null description
rows := sqlmock.NewRows([]string{"id", "name", "description", ...}).
AddRow("plan-123", "Basic Plan", nil, ...)
mock.ExpectQuery(`SELECT.*`).WillReturnRows(rows)
plan, err := repo.GetByID("plan-123")
assert.NoError(t, err)
assert.Nil(t, plan.Description)Comprehensive error scenarios are tested:
- Connection failures: Database connection errors
- Deadlock simulation: Concurrent update conflicts
- Constraint violations: Foreign key and unique constraints
- Data type mismatches: Invalid data scanning
- Permission errors: Access denied scenarios
- ✅ Create plan with/without description
- ✅ Get plan by ID (found/not found)
- ✅ Get plans by merchant ID with pagination
- ✅ Update plan (success/not found)
- ✅ Delete plan (success/not found)
- ✅ Get active plans by merchant ID
- ✅ Null value handling
- ✅ Scan error handling
- ✅ Create subscription with/without trial period
- ✅ Get subscription by ID (found/not found)
- ✅ Get subscriptions by customer ID with pagination
- ✅ Get subscriptions by merchant ID with pagination
- ✅ Get subscriptions by plan ID with pagination
- ✅ Update subscription (success/not found)
- ✅ Update status (success/not found)
- ✅ Cancel subscription (immediate/period end)
- ✅ Get active subscriptions by merchant ID
- ✅ Get subscriptions due for billing
- ✅ Null value handling for all time fields
- ✅ Scan error handling
- ✅ Store event with/without optional fields
- ✅ Get pending events (pending/failed/retry)
- ✅ Get event by ID (found/not found)
- ✅ Update status with/without error message
- ✅ Mark as processing with race condition protection
- ✅ Increment retry count with backoff
- ✅ Delete completed events
- ✅ Null value handling
- ✅ Scan error handling
- ✅ Retry logic simulation
- ✅ Database connection failures
- ✅ Deadlock simulation
- ✅ Concurrent access patterns
- ✅ Large data handling
- ✅ Empty result sets
- ✅ Retry logic with various error types
- All SQL queries use parameterized statements to prevent SQL injection
- Mock expectations verify exact parameter binding
- Tests include malformed data scenarios
- Error messages are tested to ensure they don't expose sensitive information
- Database errors are wrapped with appropriate error messages
- Stack traces are not exposed in production error responses
- Concurrent access tests verify race condition protection
- Deadlock scenarios are properly handled
- Atomic operations are tested for consistency
- Tests verify proper use of indexes through WHERE clauses
- Pagination is tested to prevent large result sets
- LIMIT clauses are properly enforced
- Database connections are properly closed in tests
- Row iteration errors are handled gracefully
- Memory usage is controlled through proper result set handling
go get github.com/DATA-DOG/go-sqlmock
go get github.com/stretchr/testify
go get github.com/google/uuid# Run all repository tests
go test ./internal/repositories/...
# Run outbox tests
go test ./internal/outbox/...
# Run with coverage
go test -cover ./internal/repositories/...
go test -cover ./internal/outbox/...
# Run with coverage report
go test -coverprofile=coverage.out ./internal/...
go tool cover -html=coverage.out- Minimum coverage: 95%
- Target coverage: 98%+
- Critical paths: 100% coverage
- Each test uses a fresh mock database connection
- Test data is isolated between test cases
- Mock expectations are verified after each test
- UUIDs are generated for unique identifiers
- Time values use consistent time zones
- Test data follows realistic business constraints
- Tests run on every pull request
- Coverage thresholds are enforced
- Performance benchmarks are monitored
- All tests must pass before merge
- Coverage requirements must be met
- No new test failures introduced
- Group related tests in table-driven format
- Use descriptive test names
- Maintain test data consistency
- Always verify mock expectations
- Use
sqlmock.AnyArg()for dynamic values - Test both success and failure scenarios
- Test all error paths
- Verify error message content
- Ensure proper resource cleanup
- Performance load testing
- Integration testing with real database
- Chaos engineering scenarios
- Common test data builders
- Reusable mock helpers
- Automated test data generation
- Test execution time tracking
- Coverage trend analysis
- Test stability metrics
The repository unit tests provide comprehensive coverage of all database operations while ensuring security, performance, and reliability. The SQL mocking approach allows for fast, isolated tests without external dependencies while maintaining high confidence in the correctness of the repository layer.