Testing
Testing Conventions
Per-service testing patterns, frameworks, fixtures, and how to run tests.
Quick Reference
| Service | Framework | Command | Notes |
|---|---|---|---|
| sirloin | testify + testcontainers | cd apps/sirloin && make lint && make run-tests | Always with -race. Integration: make run-tests-all |
| brain | Jest + NestJS testing | cd apps/brain && pnpm lint && pnpm tsc && pnpm test | NestJS test modules |
| brisket | Vitest + Testing Library | cd apps/brisket && pnpm lint && pnpm typecheck && pnpm test | NOT Jest |
| flank | Vitest + TypeScript | cd apps/flank && pnpm typecheck && pnpm lint && pnpm test && pnpm validate:seeds | Validates workflow seed JSON |
| fennec | TypeScript + ESLint | cd apps/fennec && pnpm lint && pnpm tsc | max-warnings 0 on lint |
| strip | testify | cd apps/strip && make run-tests | Race detector enabled |
| round | Go testing | cd apps/round && make run-tests | Race detector enabled |
| API/e2e tests | Playwright | cd tests && npm run test:api | Uses tests/playwright.config.ts |
sirloin (Go)
Test Infrastructure
Located in apps/sirloin/internal/pkg/testingsuite/:
suite.go—BaseTestSuite(test database, storage, config) andGRPCTestSuite(adds in-memory gRPC server via bufconn)database.go—TestDatabaseusing testcontainers (Docker Postgres), schema recreated per suitefixtures.go—FixtureBuilderwith functional options patternminioserver.go— MinIO test server for S3 operationsmocks.go— Common mock setup helpers
Test Structure
type MyTestSuite struct { testingsuite.GRPCTestSuite // or BaseTestSuite handler *Handler mockService *MockService}
func (s *MyTestSuite) SetupSuite() { s.GRPCTestSuite.SetupSuite() // creates DB, storage, gRPC server s.mockService = NewMockService(s.T()) s.handler = NewHandler(s.Storage, s.mockService)}
func (s *MyTestSuite) SetupTest() { s.GRPCTestSuite.SetupTest() // resets database s.mockService.ExpectedCalls = nil // reset mocks s.mockService.Calls = nil}
func (s *MyTestSuite) TestSomething() { ctx := s.Context() // arrange: use FixtureBuilder fb := testingsuite.NewFixtureBuilder(s.TestDB) credit, _ := fb.CreateCredit(ctx, "user_123", testingsuite.WithImageCredits(50)) char, _ := fb.CreateCharacter(ctx, "user_123", testingsuite.WithCharacterStatus("AVAILABLE"))
// act resp, err := s.handler.DoSomething(ctx, req)
// assert s.Require().NoError(err) s.Equal(expected, resp.Field)}
func TestMySuite(t *testing.T) { suite.Run(t, new(MyTestSuite))}Fixture Builder
The FixtureBuilder (apps/sirloin/internal/pkg/testingsuite/fixtures.go) uses functional options:
fb := testingsuite.NewFixtureBuilder(s.TestDB)
// Creditsfb.CreateCredit(ctx, userID, testingsuite.WithImageCredits(100), testingsuite.WithFullAccessCredits(50))
// Charactersfb.CreateCharacter(ctx, userID, testingsuite.WithCharacterID(id), testingsuite.WithCharacterName("Test"), testingsuite.WithCharacterStatus("CHARACTER_STATUS_AVAILABLE"), testingsuite.WithCharacterGender("FEMALE"))
// Mediafb.CreateMedia(ctx, userID, characterID, testingsuite.WithMediaType("IMAGE"), testingsuite.WithMediaStatus("COMPLETED"), testingsuite.WithMediaImagePath("/test/image.jpg"))Mock Generation
mockery v3 configured in apps/sirloin/.mockery.yml. Mocks live in the same directory as interfaces with mock_*.go naming.
cd apps/sirloin && make generate-mocksIntegration Tests
- Tagged with
sandboxorintegrationbuild tags - Run via
make run-tests-all(sequential with-p 1) - Need
SIRLOIN_CHARGEBEE_*env vars for Chargebee sandbox tests - Testcontainers for Postgres (Docker required)
- MinIO for S3 (enabled via
EnableMinIOTestServer())
Running a Single Test
cd apps/sirloingo test -v -race -run TestCreateCharacter_Success ./internal/app/services/characters/brain (NestJS)
Framework
Jest with NestJS test modules.
Pattern
describe('MyService', () => { let service: MyService; let mockDep: MockType<DependencyService>;
beforeEach(async () => { const module = await Test.createTestingModule({ providers: [ MyService, { provide: DependencyService, useValue: { method: jest.fn() } }, ], }).compile();
service = module.get(MyService); mockDep = module.get(DependencyService); });
it('should do something', async () => { mockDep.method.mockResolvedValue(expected); const result = await service.doSomething(); expect(result).toEqual(expected); });});Test Files
- Convention:
*.spec.tsnext to source files - Location: same directory as the module being tested
brisket (Next.js)
Framework
Vitest + @testing-library/react. Not Jest — this is a common gotcha.
Running
cd apps/brisketpnpm test # run all testspnpm test:watch # watch modeflank (TanStack Start)
Running
cd apps/flankpnpm typecheckpnpm lintpnpm testpnpm validate:seedsfennec (React)
Framework
Vitest + @testing-library/react + @testing-library/jest-dom
Strictness
ESLint runs with max-warnings 0 — any lint warning fails CI.
Running
cd apps/fennecpnpm lintpnpm tscPlaywright API Tests
The shared Playwright API and e2e tests live under tests/ with configuration in tests/playwright.config.ts.
cd testsnpm run test:apiGeneral Rules
- Go services always use
-raceflag - Each test gets fresh state — database reset per test in sirloin, fresh module per test in brain
- No shared mutable state between tests
- Integration tests are separate from unit tests (build tags in Go, separate scripts in TS)
- Mocks are reset in
SetupTest()(Go) orbeforeEach()(TS) — not shared across tests