writing-tests
Principles for writing effective, maintainable tests. Covers naming conventions, assertion best practices, and comprehensive edge case checklists. Based on BugMagnet by Gojko Adzic. Triggers on: writing any test, 'add tests', test review, test naming, assertion choices, edge case coverage, 'what should I test', test structure decisions.
What this skill does
# Writing Tests
How to write tests that catch bugs, document behavior, and remain maintainable.
> Based on [BugMagnet](https://github.com/gojko/bugmagnet-ai-assistant) by Gojko Adzic. Adapted with attribution.
## Critical Rules
π¨ **Test names describe outcomes, not actions.** "returns empty array when input is null" not "test null input". The name IS the specification.
π¨ **Assertions must match test titles.** If the test claims to verify "different IDs", assert on the actual ID valuesβnot just count or existence.
π¨ **Assert specific values, not types.** `expect(result).toEqual(['First.', ' Second.'])` not `expect(result).toBeDefined()`. Specific assertions catch specific bugs.
π¨ **One concept per test.** Each test verifies one behavior. If you need "and" in your test name, split it.
π¨ **Bugs cluster together.** When you find one bug, test related scenarios. The same misunderstanding often causes multiple failures.
## When This Applies
- Writing new tests
- Reviewing test quality
- During TDD RED phase (writing the failing test)
- Expanding test coverage
- Investigating discovered bugs
## Test Naming
**Pattern:** `[outcome] when [condition]`
### Good Names (Describe Outcomes)
```
returns empty array when input is null
throws ValidationError when email format invalid
calculates tax correctly for tax-exempt items
preserves original order when duplicates removed
```
### Bad Names (Describe Actions)
```
test null input // What about null input?
should work // What does "work" mean?
handles edge cases // Which edge cases?
email validation test // What's being validated?
```
### The Specification Test
Your test name should read like a specification. If someone reads ONLY the test names, they should understand the complete behavior of the system.
## Assertion Best Practices
### Assert Specific Values
```typescript
// β WEAK - passes even if completely wrong data
expect(result).toBeDefined()
expect(result.items).toHaveLength(2)
expect(user).toBeTruthy()
// β
STRONG - catches actual bugs
expect(result).toEqual({ status: 'success', items: ['a', 'b'] })
expect(user.email).toBe('[email protected]')
```
### Match Assertions to Test Title
```typescript
// β TEST SAYS "different IDs" BUT ASSERTS COUNT
it('generates different IDs for each call', () => {
const id1 = generateId()
const id2 = generateId()
expect([id1, id2]).toHaveLength(2) // WRONG: doesn't check they're different!
})
// β
ACTUALLY VERIFIES DIFFERENT IDs
it('generates different IDs for each call', () => {
const id1 = generateId()
const id2 = generateId()
expect(id1).not.toBe(id2) // RIGHT: verifies the claim
})
```
### Avoid Implementation Coupling
```typescript
// β BRITTLE - tests implementation details
expect(mockDatabase.query).toHaveBeenCalledWith('SELECT * FROM users WHERE id = 1')
// β
FLEXIBLE - tests behavior
expect(result.user.name).toBe('Alice')
```
## Test Structure
### Arrange-Act-Assert
```typescript
it('calculates total with tax for non-exempt items', () => {
// Arrange: Set up test data
const item = { price: 100, taxExempt: false }
const taxRate = 0.1
// Act: Execute the behavior
const total = calculateTotal(item, taxRate)
// Assert: Verify the outcome
expect(total).toBe(110)
})
```
### One Concept Per Test
```typescript
// β MULTIPLE CONCEPTS - hard to diagnose failures
it('validates and processes order', () => {
expect(validate(order)).toBe(true)
expect(process(order).status).toBe('complete')
expect(sendEmail).toHaveBeenCalled()
})
// β
SINGLE CONCEPT - clear failures
it('accepts valid orders', () => {
expect(validate(validOrder)).toBe(true)
})
it('rejects orders with negative quantities', () => {
expect(validate(negativeQuantityOrder)).toBe(false)
})
it('sends confirmation email after processing', () => {
process(order)
expect(sendEmail).toHaveBeenCalledWith(order.customerEmail)
})
```
## Edge Case Checklists
When testing a function, systematically consider these edge cases based on input types.
### Numbers
- [ ] Zero
- [ ] Negative numbers
- [ ] Very large numbers (near MAX_SAFE_INTEGER)
- [ ] Very small numbers (near MIN_SAFE_INTEGER)
- [ ] Decimal precision (0.1 + 0.2)
- [ ] NaN
- [ ] Infinity / -Infinity
- [ ] Boundary values (off-by-one at limits)
### Strings
- [ ] Empty string `""`
- [ ] Whitespace only `" "`
- [ ] Very long strings (10K+ characters)
- [ ] Unicode: emojis π¨βπ©βπ§βπ¦, RTL text, combining characters
- [ ] Special characters: quotes, backslashes, null bytes
- [ ] SQL/HTML/script injection patterns
- [ ] Leading/trailing whitespace
- [ ] Mixed case sensitivity
### Collections (Arrays, Objects, Maps)
- [ ] Empty collection `[]`, `{}`
- [ ] Single element
- [ ] Duplicates
- [ ] Nested structures
- [ ] Circular references
- [ ] Very large collections (performance)
- [ ] Sparse arrays
- [ ] Mixed types in arrays
### Dates and Times
- [ ] Leap years (Feb 29)
- [ ] Daylight saving transitions
- [ ] Timezone boundaries
- [ ] Midnight (00:00:00)
- [ ] End of day (23:59:59)
- [ ] Year boundaries (Dec 31 β Jan 1)
- [ ] Invalid dates (Feb 30, Month 13)
- [ ] Unix epoch edge cases
- [ ] Far future/past dates
### Null and Undefined
- [ ] `null` input
- [ ] `undefined` input
- [ ] Missing optional properties
- [ ] Explicit `undefined` vs missing key
### Domain-Specific
- [ ] Email: valid formats, edge cases (plus signs, subdomains)
- [ ] URLs: protocols, ports, special characters, relative paths
- [ ] Phone numbers: international formats, extensions
- [ ] Addresses: Unicode, multi-line, missing components
- [ ] Currency: rounding, different currencies, zero amounts
- [ ] Percentages: 0%, 100%, over 100%
### Violated Domain Constraints
These test implicit assumptions in your domain:
- [ ] Uniqueness violations (duplicate IDs, emails)
- [ ] Missing required relationships (orphaned records)
- [ ] Ordering violations (events out of sequence)
- [ ] Range breaches (age -1, quantity 1000000)
- [ ] State inconsistencies (shipped but not paid)
- [ ] Format mismatches (expected JSON, got XML)
- [ ] Temporal ordering (end before start)
### Typed Property Validation
When testing code that validates properties against type constraints (e.g., validating `route: string` in an interface):
**Wrong-type literals:**
- [ ] Numeric literal when string expected (`route = 123`)
- [ ] Boolean literal when string expected (`route = true`)
- [ ] String literal when number expected (`count = 'five'`)
- [ ] String literal when boolean expected (`enabled = 'yes'`)
**Non-literal expressions:**
- [ ] Template literal (`` route = `/path/${id}` ``)
- [ ] Variable reference (`route = someVariable`)
- [ ] Function call (`route = getRoute()`)
- [ ] Computed property (`route = config.path`)
**Correct type:**
- [ ] Valid literal of correct type (`route = '/orders'`)
- [ ] Edge values (empty string `''`, zero `0`, `false`)
**Why this matters:**
A common bug pattern is validating "is this a literal?" without checking "is this the RIGHT TYPE of literal?"
- `hasLiteralValue()` returns true for `123`, `true`, and `'string'`
- `hasStringLiteralValue()` returns true only for `'string'`
When an interface specifies `property: string`, validation must reject numeric and boolean literals, not just non-literal expressions.
## Bug Clustering
When you discover a bug, don't stopβexplore related scenarios:
1. **Same function, similar inputs** - If null fails, test undefined, empty string
2. **Same pattern, different locations** - If one endpoint mishandles auth, check others
3. **Same developer assumption** - If off-by-one here, check other boundaries
4. **Same data type** - If dates fail at DST, check other time edge cases
## When Tempted to Cut Corners
- If your test name says "test" or "should work": STOP. What outcome are you actually verifying? Name it specifically.
- If you're asserting `toBeDefined()` or `toBeTruthy()`: STOP. What value do you actually expect? Assert that iRelated in Writing & Docs
jax-development
IncludedUse this skill when the user is writing, debugging, profiling, refactoring, reviewing, benchmarking, parallelising, exporting, or explaining JAX code, or when they mention JAX, jax.numpy, jit, grad, value_and_grad, vmap, scan, lax, random keys, pytrees, jax.Array, sharding, Mesh, PartitionSpec, NamedSharding, pmap, shard_map, Pallas, XLA, StableHLO, checkify, profiler, or the JAX repo. It helps turn NumPy or PyTorch-style code into pure functional JAX, fix tracer/control-flow/shape/PRNG bugs, remove recompiles and host-device syncs, choose transforms and sharding strategies, inspect jaxpr/lowering/IR, and benchmark compiled code correctly.
nature-article-writer
IncludedDrafts, rewrites, diagnostically critiques, and style-calibrates primary research manuscripts for Nature and Nature Portfolio journals. Use when the user wants a Nature-style title, summary paragraph or abstract, introduction, results, discussion, methods, figure legends, presubmission enquiry, cover letter, reviewer response, or when a scientific draft sounds generic, jargon-heavy, structurally weak, or AI-ish and needs precise, broad-reader-friendly prose without inventing data, analyses, or references. Best for primary research articles and letters rather than reviews or press releases unless explicitly adapting one.
deckrd
IncludedDocument-driven framework that derives requirements, specifications, implementation plans, and executable tasks from goals through structured AI dialogue. Use when user says "write requirements", "create spec", "plan implementation", "derive tasks", "structure this feature", "break down into tasks", or "document this module". Also use for reverse engineering existing code into docs (/deckrd rev). Do NOT use for direct code writing β use /deckrd-coder after tasks are generated. Do NOT use when the user only wants to run or fix existing code without planning.
clinical-decision-support
IncludedGenerate professional clinical decision support (CDS) documents for pharmaceutical and clinical research settings, including patient cohort analyses (biomarker-stratified with outcomes) and treatment recommendation reports (evidence-based guidelines with decision algorithms). Supports GRADE evidence grading, statistical analysis (hazard ratios, survival curves, waterfall plots), biomarker integration, and regulatory compliance. Outputs publication-ready LaTeX/PDF format optimized for drug development, clinical research, and evidence synthesis.
handling-sf-data
IncludedSalesforce data operations with 130-point scoring. Use this skill to create, update, delete, bulk import/export, generate test data, and clean up org records using sf CLI and anonymous Apex. TRIGGER when: user creates test data, performs bulk import/export, uses sf data CLI commands, needs data factory patterns for Apex tests, or needs to seed/clean records in a Salesforce org. DO NOT TRIGGER when: SOQL query writing only (use querying-soql), Apex test execution (use running-apex-tests), or metadata deployment (use deploying-metadata).
accelint-ac-to-playwright
IncludedConvert and validate acceptance criteria for Playwright test automation. Use when user asks to (1) review/evaluate/check if AC are ready for automation, (2) assess if AC can be converted as-is, (3) validate AC quality for Playwright, (4) turn AC into tests, (5) generate tests from acceptance criteria, (6) convert .md bullets or .feature Gherkin files to Playwright specs, (7) create test automation from requirements. Handles both bullet-style markdown and Gherkin syntax with JSON test plan generation and validation.