Testing
Testing Conventions
Section titled “Testing Conventions”Per-service testing patterns, frameworks, fixtures, and how to run tests.
Quick Reference
Section titled “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 |
UI-only app; no seed validation anymore |
| 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)
Section titled “sirloin (Go)”Test Infrastructure
Section titled “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
Section titled “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", func(c *models.Character) { c.Status = "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
Section titled “Fixture Builder”The FixtureBuilder (apps/sirloin/internal/pkg/testingsuite/fixtures.go) uses functional options. WithImageCredits and WithMediaImagePath are the named helpers; set any other field with an inline callback:
fb := testingsuite.NewFixtureBuilder(s.TestDB)
// Creditsfb.CreateCredit(ctx, userID, testingsuite.WithImageCredits(100), func(c *models.Credit) { c.FullAccessCredits = 50 })
// Charactersfb.CreateCharacter(ctx, userID, func(c *models.Character) { c.Name = "Test" c.Status = "CHARACTER_STATUS_AVAILABLE" c.Gender = "FEMALE"})
// Mediafb.CreateMedia(ctx, userID, characterID, testingsuite.WithMediaImagePath("/test/image.jpg"), func(m *models.Media) { m.Type = "IMAGE" m.Status = "COMPLETED" })Mock Generation
Section titled “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
Section titled “Integration 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
Section titled “Running a Single Test”cd apps/sirloingo test -v -race -run TestCreateCharacter_Success ./internal/app/services/characters/brain (NestJS)
Section titled “brain (NestJS)”Framework
Section titled “Framework”Jest with NestJS test modules.
Pattern
Section titled “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
Section titled “Test Files”- Convention:
*.spec.tsnext to source files - Location: same directory as the module being tested
brisket (Next.js)
Section titled “brisket (Next.js)”Framework
Section titled “Framework”Vitest + @testing-library/react. Not Jest — this is a common gotcha.
Running
Section titled “Running”cd apps/brisketpnpm test # run all testspnpm test:watch # watch modeflank (TanStack Start)
Section titled “flank (TanStack Start)”Running
Section titled “Running”cd apps/flankpnpm typecheckpnpm lintpnpm testfennec (React)
Section titled “fennec (React)”Framework
Section titled “Framework”Vitest + @testing-library/react + @testing-library/jest-dom
Strictness
Section titled “Strictness”ESLint runs with max-warnings 0 — any lint warning fails CI.
Running
Section titled “Running”cd apps/fennecpnpm lintpnpm tscPlaywright API Tests
Section titled “Playwright API Tests”The shared Playwright API and e2e tests live under tests/ with configuration in tests/playwright.config.ts.
cd testsnpm run test:api[!TIP] The smoke tests utilize default fallback environment variables (configured in
tests/global-setup.ts) for required sandbox entity IDs (such asBRAIN_GENERATION_CHARACTER_IDandVALID_MEDIA_ID) so they execute and pass out-of-the-box against remote preview environments.
General Rules
Section titled “General 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