Skip to content

Testing

Testing Conventions

Per-service testing patterns, frameworks, fixtures, and how to run tests.

Quick Reference

ServiceFrameworkCommandNotes
sirlointestify + testcontainerscd apps/sirloin && make lint && make run-testsAlways with -race. Integration: make run-tests-all
brainJest + NestJS testingcd apps/brain && pnpm lint && pnpm tsc && pnpm testNestJS test modules
brisketVitest + Testing Librarycd apps/brisket && pnpm lint && pnpm typecheck && pnpm testNOT Jest
flankVitest + TypeScriptcd apps/flank && pnpm typecheck && pnpm lint && pnpm test && pnpm validate:seedsValidates workflow seed JSON
fennecTypeScript + ESLintcd apps/fennec && pnpm lint && pnpm tscmax-warnings 0 on lint
striptestifycd apps/strip && make run-testsRace detector enabled
roundGo testingcd apps/round && make run-testsRace detector enabled
API/e2e testsPlaywrightcd tests && npm run test:apiUses tests/playwright.config.ts

sirloin (Go)

Test Infrastructure

Located in apps/sirloin/internal/pkg/testingsuite/:

  • suite.goBaseTestSuite (test database, storage, config) and GRPCTestSuite (adds in-memory gRPC server via bufconn)
  • database.goTestDatabase using testcontainers (Docker Postgres), schema recreated per suite
  • fixtures.goFixtureBuilder with functional options pattern
  • minioserver.go — MinIO test server for S3 operations
  • mocks.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)
// Credits
fb.CreateCredit(ctx, userID,
testingsuite.WithImageCredits(100),
testingsuite.WithFullAccessCredits(50))
// Characters
fb.CreateCharacter(ctx, userID,
testingsuite.WithCharacterID(id),
testingsuite.WithCharacterName("Test"),
testingsuite.WithCharacterStatus("CHARACTER_STATUS_AVAILABLE"),
testingsuite.WithCharacterGender("FEMALE"))
// Media
fb.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.

Terminal window
cd apps/sirloin && make generate-mocks

Integration Tests

  • Tagged with sandbox or integration build 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

Terminal window
cd apps/sirloin
go 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.ts next 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

Terminal window
cd apps/brisket
pnpm test # run all tests
pnpm test:watch # watch mode

flank (TanStack Start)

Running

Terminal window
cd apps/flank
pnpm typecheck
pnpm lint
pnpm test
pnpm validate:seeds

fennec (React)

Framework

Vitest + @testing-library/react + @testing-library/jest-dom

Strictness

ESLint runs with max-warnings 0 — any lint warning fails CI.

Running

Terminal window
cd apps/fennec
pnpm lint
pnpm tsc

Playwright API Tests

The shared Playwright API and e2e tests live under tests/ with configuration in tests/playwright.config.ts.

Terminal window
cd tests
npm run test:api

General Rules

  1. Go services always use -race flag
  2. Each test gets fresh state — database reset per test in sirloin, fresh module per test in brain
  3. No shared mutable state between tests
  4. Integration tests are separate from unit tests (build tags in Go, separate scripts in TS)
  5. Mocks are reset in SetupTest() (Go) or beforeEach() (TS) — not shared across tests