Language Testing Patterns
Universal Principles
What to Unit Test
- Pure functions, transformations, business logic
- Complex conditionals and state transitions
- Error handling paths
- Edge cases: empty arrays, null/undefined, boundary values
What NOT to Unit Test
- Simple getters/setters, pass-through functions
- Framework internals (React rendering, Express routing)
- Implementation details -- test behavior, not structure
- Config/settings values (defaults, env var assignments, constants)
- Constructor assignments (
this.x = x tests the language, not your code)
- Route/endpoint registration (test handler logic instead)
- Enum values and constants
- "Renders without crashing" with no behavior assertion
- Test code (test helpers, fixtures, factories, mocks, test utilities)
- Wiring/glue code with no logic
Every test must exercise a decision point, transformation, or behavior path.
Test User Stories, Not Internals
- Focus tests on verifying key user stories / user needs, not implementation details
- Test public interfaces / APIs -- not private methods or internal state
- Coverage hierarchy: important user story coverage > branch coverage > line coverage
- Write a failing test for user-reported bugs before fixing
- Avoid testing trivial functionality (framework-generated getters/setters,
@ConfigurationProperties classes, constructor assignments)
Coverage Opinion
- 80% line coverage as gate, focus on branch coverage for business logic
- High coverage != well-tested. Missing edge cases matters more than line count.
- Exclude:
.d.ts, config files, generated code, migrations, __repr__, if TYPE_CHECKING, test files, test helpers, test factories
Factory Fixtures Over Inline Data
# Python with faker
@pytest.fixture
def make_user(db_session):
def _make_user(**kwargs):
user = UserFactory.build(**kwargs)
db_session.add(user)
db_session.flush()
return user
return _make_user
// JavaScript with faker
function createUser(overrides?: Partial<User>): User {
return {
id: faker.string.uuid(),
name: faker.person.fullName(),
email: faker.internet.email(),
...overrides,
};
}
Why: Returns a callable -- tests create exactly what they need. Avoids "magic values" scattered across tests.
Testing Pyramid (Shift Left)
/ E2E \ Expensive, slow, run infrequently (release testing)
/----------\
/ Integration \ Moderate cost, run in CI
/----------------\
/ Unit Tests \ Cheap, fast, run early and often
/____________________\
- Unit tests: Bulk of test coverage. Fast, isolated, catch logic errors early
- Integration tests: Verify component interactions (DB, APIs, message queues). Run in CI
- E2E tests: Validate key user stories end-to-end. Most expensive, run for release verification
- Shift left: Identify defects as early as possible where they're cheapest to fix
- Rule of thumb: if a bug can be caught by a unit test, don't rely on integration/E2E to find it
Ship Test Utilities with Components
When writing libraries or shared components, provide test utilities that make it easy for consumers to test:
- In-memory fakes / test doubles for your classes (e.g.,
InMemoryUserRepository alongside UserRepository)
- Context managers / test fixtures (Python: pytest fixtures; JS: setup helpers) to auto-configure test doubles
- Spring Boot: provide auto-configuration for test doubles via
@TestConfiguration
- Why: lowers the barrier for consumers to write tests, promotes uniformity in testing patterns across the codebase
Language-Specific Patterns
For detailed language-specific patterns, see the corresponding reference files:
- Python (pytest): See
references/testing/python-testing-patterns.md -- fixtures, monkeypatch, parametrize, conftest strategy, CI markers
- JavaScript/TypeScript (Vitest/Jest): See
references/testing/javascript-testing-patterns.md -- DI over module mocking, async testing, component testing, msw, mock hygiene
Python Quick Reference
- pytest + pytest-asyncio + pytest-cov
monkeypatch > unittest.mock (auto-reverts)
- Patch where it's used, not where it's defined
- Always use
spec=True when mocking classes
yield + cleanup in fixtures, rollback() not commit()
JS/TS Quick Reference
- Vitest for Vite projects, Jest otherwise
- DI > module mocking (
vi.mock is a last resort)
userEvent > fireEvent, getByRole > getByTestId
- Always
await async assertions
vi.clearAllMocks() in beforeEach, not afterEach
Test Generation Patterns
Naming Convention
Test names describe behavior, not implementation:
| Pattern |
Example |
should [behavior] when [condition] |
should_reject_login_when_password_expired |
test_{function}_{scenario}_{expected} |
test_calculate_discount_bulk_order_20pct |
Avoid: test_method_name, testCase1, names referencing internal method names.
Arrange-Act-Assert Structure
def test_user_creation_with_valid_data():
# Arrange
data = {"name": "Alice", "email": "alice@example.com"}
# Act
user = create_user(data)
# Assert
assert user.name == "Alice"
assert user.email == "alice@example.com"
Coverage Gap Detection Workflow
- Run coverage:
pytest --cov=src --cov-report=json
- Parse JSON for
missing_lines per file
- Prioritize by complexity: branches > lines, business logic > utils
- Generate tests for uncovered paths
Mock Generation
@pytest.fixture
def mock_api_client():
mock = Mock(spec=APIClient)
mock.fetch.return_value = {"status": "ok"}
return mock
- Always use
spec= to catch attribute errors
- Return realistic data shapes, not
"mocked_result"
Gotchas
Python
- Fixture scope leaks: module/session fixtures with mutable state
autouse fixtures create invisible dependencies
- Patching at wrong location (where defined vs. where used)
- Missing
yield in fixtures (cleanup never runs)
- High coverage on
tests/ directory (meaningless, exclude it)
JavaScript
- Using
fireEvent instead of userEvent (misses real interactions)
- Snapshot tests for components (maintenance burden, no value)
- Module mocking when DI would work (breaks on refactors)
- Not awaiting async assertions (tests pass when they shouldn't)
data-testid as first choice (tests implementation, not behavior)
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: language-testing-patterns3description: Language-specific test patterns, fixtures, and mocking strategies for Python and JS/TS. Use when designing test suites, choosing fixtures/mocking strategies, or implementing language-specific test patterns for Python and JS/TS. Do NOT use for E2E browser testing (use e2e-testing-patterns) or shell script testing (use shell-testing). Use when this capability is needed.4---56# Language Testing Patterns78## Universal Principles910### What to Unit Test11- Pure functions, transformations, business logic12- Complex conditionals and state transitions13- Error handling paths14- Edge cases: empty arrays, null/undefined, boundary values1516### What NOT to Unit Test17- Simple getters/setters, pass-through functions18- Framework internals (React rendering, Express routing)19- Implementation details -- test behavior, not structure20- Config/settings values (defaults, env var assignments, constants)21- Constructor assignments (`this.x = x` tests the language, not your code)22- Route/endpoint registration (test handler logic instead)23- Enum values and constants24- "Renders without crashing" with no behavior assertion25- Test code (test helpers, fixtures, factories, mocks, test utilities)26- Wiring/glue code with no logic2728**Every test must exercise a decision point, transformation, or behavior path.**2930### Test User Stories, Not Internals31- Focus tests on verifying key **user stories / user needs**, not implementation details32- Test **public interfaces / APIs** -- not private methods or internal state33- Coverage hierarchy: **important user story coverage > branch coverage > line coverage**34- Write a failing test for user-reported bugs **before** fixing35- Avoid testing trivial functionality (framework-generated getters/setters, `@ConfigurationProperties` classes, constructor assignments)3637### Coverage Opinion38- 80% line coverage as gate, focus on branch coverage for business logic39- High coverage != well-tested. Missing edge cases matters more than line count.40- Exclude: `.d.ts`, config files, generated code, migrations, `__repr__`, `if TYPE_CHECKING`, test files, test helpers, test factories4142### Factory Fixtures Over Inline Data43```python44# Python with faker45@pytest.fixture46def make_user(db_session):47 def _make_user(**kwargs):48 user = UserFactory.build(**kwargs)49 db_session.add(user)50 db_session.flush()51 return user52 return _make_user53```5455```typescript56// JavaScript with faker57function createUser(overrides?: Partial<User>): User {58 return {59 id: faker.string.uuid(),60 name: faker.person.fullName(),61 email: faker.internet.email(),62 ...overrides,63 };64}65```6667**Why**: Returns a callable -- tests create exactly what they need. Avoids "magic values" scattered across tests.6869## Testing Pyramid (Shift Left)7071```72 / E2E \ Expensive, slow, run infrequently (release testing)73 /----------\74 / Integration \ Moderate cost, run in CI75 /----------------\76 / Unit Tests \ Cheap, fast, run early and often77 /____________________\78```7980- **Unit tests**: Bulk of test coverage. Fast, isolated, catch logic errors early81- **Integration tests**: Verify component interactions (DB, APIs, message queues). Run in CI82- **E2E tests**: Validate key user stories end-to-end. Most expensive, run for release verification83- **Shift left**: Identify defects as early as possible where they're cheapest to fix84- Rule of thumb: if a bug can be caught by a unit test, don't rely on integration/E2E to find it8586## Ship Test Utilities with Components8788When writing libraries or shared components, provide test utilities that make it easy for consumers to test:8990- **In-memory fakes / test doubles** for your classes (e.g., `InMemoryUserRepository` alongside `UserRepository`)91- **Context managers / test fixtures** (Python: pytest fixtures; JS: setup helpers) to auto-configure test doubles92- **Spring Boot**: provide auto-configuration for test doubles via `@TestConfiguration`93- **Why**: lowers the barrier for consumers to write tests, promotes uniformity in testing patterns across the codebase9495## Language-Specific Patterns9697For detailed language-specific patterns, see the corresponding reference files:9899- **Python (pytest)**: See `references/testing/python-testing-patterns.md` -- fixtures, monkeypatch, parametrize, conftest strategy, CI markers100- **JavaScript/TypeScript (Vitest/Jest)**: See `references/testing/javascript-testing-patterns.md` -- DI over module mocking, async testing, component testing, msw, mock hygiene101102### Python Quick Reference103- pytest + pytest-asyncio + pytest-cov104- `monkeypatch` > `unittest.mock` (auto-reverts)105- Patch where it's used, not where it's defined106- Always use `spec=True` when mocking classes107- `yield` + cleanup in fixtures, `rollback()` not `commit()`108109### JS/TS Quick Reference110- Vitest for Vite projects, Jest otherwise111- DI > module mocking (`vi.mock` is a last resort)112- `userEvent` > `fireEvent`, `getByRole` > `getByTestId`113- Always `await` async assertions114- `vi.clearAllMocks()` in `beforeEach`, not `afterEach`115116## Test Generation Patterns117118### Naming Convention119120Test names describe **behavior**, not implementation:121122| Pattern | Example |123|---|---|124| `should [behavior] when [condition]` | `should_reject_login_when_password_expired` |125| `test_{function}_{scenario}_{expected}` | `test_calculate_discount_bulk_order_20pct` |126127Avoid: `test_method_name`, `testCase1`, names referencing internal method names.128129### Arrange-Act-Assert Structure130```python131def test_user_creation_with_valid_data():132 # Arrange133 data = {"name": "Alice", "email": "alice@example.com"}134135 # Act136 user = create_user(data)137138 # Assert139 assert user.name == "Alice"140 assert user.email == "alice@example.com"141```142143### Coverage Gap Detection Workflow1441. Run coverage: `pytest --cov=src --cov-report=json`1452. Parse JSON for `missing_lines` per file1463. Prioritize by complexity: branches > lines, business logic > utils1474. Generate tests for uncovered paths148149### Mock Generation150```python151@pytest.fixture152def mock_api_client():153 mock = Mock(spec=APIClient)154 mock.fetch.return_value = {"status": "ok"}155 return mock156```157158- Always use `spec=` to catch attribute errors159- Return realistic data shapes, not `"mocked_result"`160161## Gotchas162163### Python164- Fixture scope leaks: module/session fixtures with mutable state165- `autouse` fixtures create invisible dependencies166- Patching at wrong location (where defined vs. where used)167- Missing `yield` in fixtures (cleanup never runs)168- High coverage on `tests/` directory (meaningless, exclude it)169170### JavaScript171- Using `fireEvent` instead of `userEvent` (misses real interactions)172- Snapshot tests for components (maintenance burden, no value)173- Module mocking when DI would work (breaks on refactors)174- Not awaiting async assertions (tests pass when they shouldn't)175- `data-testid` as first choice (tests implementation, not behavior)176177---178> Converted and distributed by [TomeVault](https://tomevault.io/claim/jlaws) — claim your Tome and manage your conversions.179<!-- tomevault:4.0:skill_md:2026-04-13 -->