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.
npx skills add https://github.com/NTCoding/claude-skillz --skill writing-tests
How to write tests that catch bugs, document behavior, and remain maintainable.
> Based on BugMagnet by Gojko Adzic. Adapted with attribution.
🚨 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.
Pattern: [outcome] when [condition]
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
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?
Your test name should read like a specification. If someone reads ONLY the test names, they should understand the complete behavior of the system.
// ❌ 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]')
// ❌ 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
})
// ❌ BRITTLE - tests implementation details
expect(mockDatabase.query).toHaveBeenCalledWith('SELECT * FROM users WHERE id = 1')
// ✅ FLEXIBLE - tests behavior
expect(result.user.name).toBe('Alice')
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)
})
// ❌ 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)
})
When testing a function, systematically consider these edge cases based on input types.
""" "[], {}null inputundefined inputundefined vs missing keyThese test implicit assumptions in your domain:
When testing code that validates properties against type constraints (e.g., validating route: string in an interface):
Wrong-type literals:
route = 123)route = true)count = 'five')enabled = 'yes')Non-literal expressions:
route = /path/${id} )route = someVariable)route = getRoute())route = config.path)Correct type:
route = '/orders')'', 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.
When you discover a bug, don't stop—explore related scenarios:
toBeDefined() or toBeTruthy(): STOP. What value do you actually expect? Assert that instead.With TDD Process: This skill guides the RED phase—how to write the failing test well.
With Software Design Principles: Testable code follows design principles. Hard-to-test code often has design problems.
Use when implementing any feature or bugfix, before writing implementation code
Use when implementing any feature or bugfix, before writing implementation code - write the test first, watch it fail, write minimal code to pass; ensures tests actually verify behavior by requiring failure first
Use when creating new skills, editing existing skills, or verifying skills work before deployment - applies TDD to process documentation by testing with subagents before writing, iterating until bulletproof against rationalization
Rules for writing and reviewing tests in the Remotion repository. Use whenever adding, editing, or reviewing tests, especially for Studio UI, rendering, CLI, server, media, and cross-package changes, to prefer complete integration workflows over narrow helper tests and implementation details.
Patterns and pitfalls for the ONNX domain Attention operator (opset 23/24) CUDA implementation. Use when modifying the dispatch cascade in core/providers/cuda/llm/attention.cc, writing mask/bias CUDA kernels, debugging attention test routing, or adding features to the ONNX Attention op. NOT for contrib domain MultiHeadAttention/GroupQueryAttention.
> Translates a HuggingFace model into a prefill-only AutoDeploy custom model using reference custom ops, validates with hierarchical equivalence tests.
Translates a HuggingFace model into a prefill-only AutoDeploy custom model using reference custom ops, validates with hierarchical equivalence tests.
Generates Angular 17+ standalone components, configures advanced routing with lazy loading and guards, implements NgRx state management, applies RxJS patterns, and optimizes bundle performance. Use when building Angular 17+ applications with standalone components or signals, setting up NgRx stores, establishing RxJS reactive patterns, performance tuning, or writing Angular tests for enterprise apps.
Take ntcoding/writing-tests from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.