mcpbeat Sign in

Writing Tests Skill for Claude

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.

2k tokens
context cost
the whole folder, loaded on every use
1
files
instructions only
0
copies elsewhere
how many repositories repackaged it
331
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/NTCoding/claude-skillz --skill writing-tests

The instruction itself

26 sections, as written by the author

Writing Tests

How to write tests that catch bugs, document behavior, and remain maintainable.

> Based on BugMagnet 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

// ❌ 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

// ❌ 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

// ❌ 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

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

// ❌ 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:

  • Same function, similar inputs - If null fails, test undefined, empty string
  • Same pattern, different locations - If one endpoint mishandles auth, check others
  • Same developer assumption - If off-by-one here, check other boundaries
  • 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 instead.
  • If your assertion doesn't match your test title: STOP. Either fix the assertion or rename the test. They must agree.
  • If you're testing multiple concepts in one test: STOP. Split it. Future you debugging a failure will thank you.
  • If you found a bug and wrote one test: STOP. Bugs cluster. What related scenarios might have the same problem?
  • If you're skipping edge cases because "that won't happen": STOP. It will happen. In production. At 3 AM.

Integration with Other Skills

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.

Other skills for the same job

different authors, same section of the catalogue
Test Driven Development
by w95
×7

Use when implementing any feature or bugfix, before writing implementation code

2k tokens
Test Driven Development
by ComeOnOliver
×2

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

6k tokens
Writing Skills
by ComeOnOliver
×1

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

32k tokens
Writing Tests
by remotion-dev
vendor

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.

3k tokens
Cuda Attention Kernel Patterns
by microsoft
vendor

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.

7k tokens
Ad Model Onboard
by NVIDIA
vendor

> Translates a HuggingFace model into a prefill-only AutoDeploy custom model using reference custom ops, validates with hierarchical equivalence tests.

8k tokens
Ad Model Onboard
by NVIDIA
vendor

Translates a HuggingFace model into a prefill-only AutoDeploy custom model using reference custom ops, validates with hierarchical equivalence tests.

8k tokens
Angular Architect
by Jeffallan

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.

13k tokens

How to use it

Copy the folder

Take ntcoding/writing-tests from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

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.