Skip to content

Testing

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

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

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

  • suite.go — BaseTestSuite (test database, storage, config) and GRPCTestSuite (adds in-memory gRPC server via bufconn)
  • database.go — TestDatabase using testcontainers (Docker Postgres), schema recreated per suite
  • fixtures.go — FixtureBuilder with functional options pattern
  • minioserver.go — MinIO test server for S3 operations
  • mocks.go — Common mock setup helpers
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))
}

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)
// Credits
fb.CreateCredit(ctx, userID,
testingsuite.WithImageCredits(100),
func(c *models.Credit) { c.FullAccessCredits = 50 })
// Characters
fb.CreateCharacter(ctx, userID, func(c *models.Character) {
c.Name = "Test"
c.Status = "CHARACTER_STATUS_AVAILABLE"
c.Gender = "FEMALE"
})
// Media
fb.CreateMedia(ctx, userID, characterID,
testingsuite.WithMediaImagePath("/test/image.jpg"),
func(m *models.Media) {
m.Type = "IMAGE"
m.Status = "COMPLETED"
})

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
  • 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())
Terminal window
cd apps/sirloin
go test -v -race -run TestCreateCharacter_Success ./internal/app/services/characters/

Jest with NestJS test modules.

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);
});
});
  • Convention: *.spec.ts next to source files
  • Location: same directory as the module being tested

Vitest + @testing-library/react. Not Jest — this is a common gotcha.

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

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

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

Terminal window
cd apps/fennec
pnpm lint
pnpm tsc

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

[!TIP] The smoke tests utilize default fallback environment variables (configured in tests/global-setup.ts) for required sandbox entity IDs (such as BRAIN_GENERATION_CHARACTER_ID and VALID_MEDIA_ID) so they execute and pass out-of-the-box against remote preview environments.

  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