Testing
Test Stats (Phase 1)
- 29 test functions across 10 test files
- 166 test cases (table-driven)
- All passing, race-clean
- Coverage target: 70% minimum
Running Tests
# All unit tests
make test
# With race detector
make test-race
# With coverage report
make test-coverage
# Opens coverage.html in browser
# Integration tests (requires Docker services)
make test-integration
# E2E smoke tests
make test-e2e
Test Patterns
Table-Driven Tests
All tests use table-driven patterns with testify/assert and testify/require:
func TestCalculateFinancials(t *testing.T) {
tests := []struct {
name string
dealType string
hours decimal.Decimal
expected decimal.Decimal
}{
{
name: "SUP within limit",
dealType: "SUP",
hours: decimal.NewFromFloat(30),
expected: decimal.NewFromFloat(15000000),
},
{
name: "SUP with overtime",
dealType: "SUP",
hours: decimal.NewFromFloat(50),
expected: decimal.NewFromFloat(16500000),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := calculateFinancials(tt.dealType, tt.hours)
assert.True(t, tt.expected.Equal(result.TotalAmount))
})
}
}
Handler Mocks
Handlers use function-field structs (not codegen) for mocking:
type mockInvoiceService struct {
GetByIDFunc func(ctx context.Context, id uuid.UUID) (*models.Invoice, error)
GenerateFunc func(ctx context.Context, req models.GenerateRequest) (*models.Invoice, error)
FinalizeFunc func(ctx context.Context, id uuid.UUID) (*models.Invoice, error)
}
func (m *mockInvoiceService) GetByID(ctx context.Context, id uuid.UUID) (*models.Invoice, error) {
return m.GetByIDFunc(ctx, id)
}
Database Mocking
Use go-sqlmock for PostgreSQL layer tests:
func TestGetInvoice(t *testing.T) {
db, mock, err := sqlmock.New()
require.NoError(t, err)
defer db.Close()
mock.ExpectQuery("SELECT .* FROM invoices").
WithArgs(invoiceID).
WillReturnRows(sqlmock.NewRows(columns).AddRow(values...))
// ... test logic
}
PDF Integration Test
A separate integration test validates PDF generation against the real Gotenberg instance:
# Requires access to convert.prxm.uz
go test ./internal/services/generator/ -tags=integration -run TestPDFGeneration
Test File Locations
Tests live next to the code they test:
internal/
handlers/
invoice_handler.go
invoice_handler_test.go
services/
invoice/
service.go
service_test.go
generator/
generator.go
generator_test.go
pdf_test.go
models/
invoice_test.go
CI Integration
Tests run in GitLab CI on every MR:
make lint— golangci-lint + ESLintmake test-race— Unit tests with race detectormake test-coverage— Coverage report (must exceed 70%)- JUnit XML reports in MR diffs