Claude-initial-setup test-architecture
git clone https://github.com/VersoXBT/claude-initial-setup
T=$(mktemp -d) && git clone --depth=1 https://github.com/VersoXBT/claude-initial-setup "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/testing/test-architecture" ~/.claude/skills/versoxbt-claude-initial-setup-test-architecture && rm -rf "$T"
skills/testing/test-architecture/SKILL.mdTest Architecture
Well-structured tests are maintainable, fast, and reliable. This skill covers how to organize test files, create reusable fixtures and factories, ensure test isolation, and configure test environments.
When to Use
- User is setting up a new test suite or framework
- User asks about test file organization or naming
- User needs test fixtures, factories, or seed data
- Tests are flaky due to shared state or ordering issues
- User asks about database cleanup between tests
- User needs test configuration (vitest.config, jest.config, conftest.py)
Core Patterns
Test File Organization
Co-locate tests with source files for discoverability:
src/ services/ auth.ts auth.test.ts # Unit tests next to source billing.ts billing.test.ts routes/ users.ts users.test.ts test/ integration/ api.test.ts # Integration tests in separate dir e2e/ login.spec.ts # E2E tests in dedicated dir fixtures/ users.json # Shared test data factories/ user.factory.ts # Data factories helpers/ setup.ts # Test setup utilities db.ts # Database helpers
Fixtures: Static Test Data
Fixtures are pre-defined data objects for predictable testing:
// test/fixtures/users.ts export const validUser = { email: 'test@example.com', name: 'Test User', role: 'member', } as const export const adminUser = { email: 'admin@example.com', name: 'Admin User', role: 'admin', } as const export const invalidUser = { email: 'not-an-email', name: '', role: 'unknown', } as const
Usage in tests:
import { validUser, adminUser } from '../fixtures/users' it('creates a user', async () => { const result = await createUser(validUser) expect(result.email).toBe(validUser.email) })
Factories: Dynamic Test Data
Factories generate unique test data with sensible defaults. Override only what matters for each test:
TypeScript (with custom factory):
// test/factories/user.factory.ts let counter = 0 interface UserOverrides { email?: string name?: string role?: string } export function buildUser(overrides: UserOverrides = {}) { counter += 1 return { email: `user-${counter}@test.com`, name: `User ${counter}`, role: 'member', ...overrides, } } // Usage it('creates admin user', () => { const user = buildUser({ role: 'admin' }) // user.email is unique, user.role is 'admin' })
Python (with factory_boy and Faker):
# test/factories.py import factory from faker import Faker from myapp.models import User fake = Faker() class UserFactory(factory.Factory): class Meta: model = User email = factory.LazyAttribute(lambda _: fake.email()) name = factory.LazyAttribute(lambda _: fake.name()) role = "member" # Usage def test_create_admin(): user = UserFactory(role="admin") assert user.role == "admin" assert "@" in user.email # Unique, realistic email
Test Isolation
Each test must run independently. No test should depend on another's side effects:
// test/helpers/setup.ts import { beforeEach, afterEach, afterAll } from 'vitest' import { db } from '../../src/db' beforeEach(async () => { await db.migrate.latest() // Ensure schema is current }) afterEach(async () => { await db.raw('TRUNCATE TABLE users CASCADE') await db.raw('TRUNCATE TABLE posts CASCADE') }) afterAll(async () => { await db.destroy() // Close connection pool })
Database Cleanup Strategies
Three approaches, pick one per project:
| Strategy | How | Best For |
|---|---|---|
| Truncate (recommended) | in afterEach | Most projects |
| Transaction rollback | Begin txn in beforeEach, rollback in afterEach | Fast cleanup, no cascade issues |
| Dedicated test DB | Django with fixture | Django/framework-managed |
Test Configuration
Vitest:
// vitest.config.ts import { defineConfig } from 'vitest/config' export default defineConfig({ test: { globals: true, environment: 'node', setupFiles: ['./test/helpers/setup.ts'], coverage: { provider: 'v8', reporter: ['text', 'html'], thresholds: { lines: 80, branches: 80, functions: 80 }, }, include: ['src/**/*.test.ts'], exclude: ['node_modules', 'dist'], }, })
pytest:
# pyproject.toml [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] addopts = "--strict-markers --tb=short -q" [tool.coverage.run] source = ["src"] omit = ["tests/*"] [tool.coverage.report] fail_under = 80
Anti-Patterns
- Shared mutable state between tests: Global variables modified by tests cause ordering dependencies. Each test must set up its own state.
- Not closing database connections: Forgetting
causes connection pool exhaustion and hanging test processes.afterAll(() => db.destroy()) - Testing against production database: Always use a separate test database or in-memory alternative. Never run tests against production data.
- Huge fixture files: A 500-line JSON fixture is hard to maintain. Use factories that generate data dynamically with only the relevant fields overridden.
- Skipping cleanup: Tests that leave data behind cause the next test to fail intermittently. Always truncate or rollback.
- Importing test helpers into production code: The
directory should never be imported bytest/
. Keep test utilities isolated.src/
Quick Reference
| Concept | Purpose | Location |
|---|---|---|
| Fixtures | Static, predictable test data | |
| Factories | Dynamic, unique test data | |
| Helpers | Reusable test utilities | |
| Setup | beforeEach/afterEach/afterAll | |
| Config | Test runner configuration | / |
Isolation: Each test sets up and tears down its own state. Cleanup: Truncate tables or rollback transactions after each test. Connections: Always close DB pools in afterAll. Coverage: Configure 80% threshold in test runner config.