agentleFS
ak-shipak-ship/fullstack-agent-skills/skills/test-architect/SKILL.md

test-architect

Skill0 starsChanged 6 months agoMITView on GitHub

Write unit and integration tests that actually catch bugs — real assertions, real edge cases, real failure modes. Detects the project's framework (Jest, Vitest, Mocha, pytest, Go test) and matches existing conventions. Use when the user says "write tests for", "cover this with tests", "add unit tests", "I need tests for this function", or hands over an untested module. Refuses to write placeholder `expect(true).toBe(true)` tests.

8 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

16 rules. 0 are common, 10 are distinctive, and 6 are very specific to this project.

  • 0

    Common

    Most of the 8 edited versions we read keep these.

  • 10

    Distinctive

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Do *not* trigger for: e2e/browser flows (use browser-qa), test infrastructure setup, or when the user only wants you to fix a failing test …

    line 18
  2. Distinctive:

    Before writing assertions, list the failure modes you can think of:

    line 44
  3. Distinctive:

    Pure functions: no mocks needed.

    line 105
  4. Very specific:

    HTTP: use msw (browser/node), nock, or responses (Python).

    line 107
  5. Distinctive:

    DB: prefer an in-memory or test-container instance over mocking the ORM.

    line 108
  6. Very specific:

    Time: use the framework's fake timers.

    line 109
  7. Distinctive:

    Run the file. If green, add coverage for one more failure mode you initially skipped.

    line 113
  8. Distinctive:

    Use table-driven tests for many similar cases:

    line 119
  9. Distinctive:

    Test error messages too — they're part of the contract for humans.

    line 127
  10. Very specific:

    Keep test setup local to the test or the describe block.

    line 128
  11. Distinctive:

    Don't test private methods directly.

    line 131
  12. Distinctive:

    Don't share state between tests.

    line 132
  13. Very specific:

    Don't test the framework. expect(typeof fn).toBe('function') adds nothing.

    line 133
  14. Distinctive:

    Don't snapshot-test arbitrarily.

    line 134
  15. Very specific:

    Write __tests__/slugify.test.ts with three describes: happy path (8 cases), boundary (4 cases), invalid (2 cases).

    line 142
  16. Distinctive:

    Add table-driven test for unicode normalization.

    line 143

“Common” is measured: kept by at least 6 in 10 of the 8 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

test-architect — tests that would catch the bug

When to use this skill

Trigger when the user wants tests for specific code. Strong signals:

  • "write tests for <file or function>"
  • "cover this with tests"
  • "add unit tests"
  • "I need tests for this"
  • A function pasted with no further context

Do not trigger for: e2e/browser flows (use browser-qa), test infrastructure setup, or when the user only wants you to fix a failing test (just fix it).

The output contract

Tests that:

  1. Run — npm test, pytest, go test, etc., all green on the new file.
  2. Match the codebase — same framework, same test file location, same naming convention as the existing tests.
  3. Cover the contract, not the implementation — happy path, failure modes, boundary values, empty/null inputs.
  4. Fail when behavior breaks — every assertion would detect a real regression, not just confirm the code ran.
  5. Are independent — no test depends on the order or state of another.

Workflow

1 — Detect the test stack

Inspect the repo first:

  • Which framework? Look at package.json (jest, vitest, mocha), pyproject.toml / pytest.ini, go.mod, Cargo.toml.
  • Where do tests live? __tests__/, test/, *.test.ts next to source, *_test.go?
  • What conventions? describe/it vs test(), AAA vs given/when/then, fixture style, mocking library.

Match what's there. Don't introduce a new framework just because you prefer it.

2 — Read the function like an adversary

Before writing assertions, list the failure modes you can think of:

For a function with this signature:

function parseDuration(input: string): number  // returns ms

list:

  • "30s" → 30000
  • "5m" → 300000
  • "1h" → 3600000
  • "" → throws? returns NaN? returns 0?
  • "abc" → throws?
  • "5" → no unit — throws or assumes ms?
  • "5x" → unknown unit — throws?
  • "-5s" → negative — allowed?
  • null, undefined → TypeScript prevents at compile, but runtime?
  • Float input: "1.5s" → 1500?

That list is the test plan. Now write one test per item.

3 — Structure the test file

For each function under test, group:

describe('parseDuration', () => {
  describe('happy path', () => { ... })
  describe('boundary values', () => { ... })
  describe('invalid input', () => { ... })
})

Name tests with the input → output, not the implementation:

✅ 'parses "30s" as 30000' ✅ 'throws on empty string' ❌ 'works' ❌ 'returns the result' ❌ 'test 1'

4 — Write real assertions

Each test asserts a specific observable. Never:

expect(result).toBeTruthy()      // ❌ what does truthy mean?
expect(result).toBeDefined()     // ❌ a string '' is defined too
expect(true).toBe(true)          // ❌ this is not a test

Always:

expect(result).toBe(30000)
expect(result).toEqual({ id: 1, name: 'Ada' })
expect(() => parseDuration('')).toThrow(/empty/)
expect(result.items).toHaveLength(3)

For async: await expect(promise).rejects.toThrow(SpecificError).

5 — Stub at the right layer

  • Pure functions: no mocks needed.
  • Functions that call other modules: stub at the import boundary, not deep inside.
  • HTTP: use msw (browser/node), nock, or responses (Python). Don't stub fetch globally — that breaks other tests.
  • DB: prefer an in-memory or test-container instance over mocking the ORM. Mocks of complex ORMs lie.
  • Time: use the framework's fake timers; never await new Promise(r => setTimeout(r, ...)) in tests.

6 — Run, then expand

Run the file. If green, add coverage for one more failure mode you initially skipped. If red, the test caught a real bug — flag it to the user before "fixing" the test.

Patterns and anti-patterns

✅ Do:

  • One concept per test. If you need && in the name, split it.
  • Use table-driven tests for many similar cases:
    test.each([
      ['30s', 30000], ['5m', 300000], ['1h', 3600000],
    ])('parses %s as %i', (input, expected) => {
      expect(parseDuration(input)).toBe(expected)
    })
  • Test error messages too — they're part of the contract for humans.
  • Keep test setup local to the test or the describe block. Avoid module-level mutation.

❌ Don't:

  • Don't test private methods directly. Test the public contract.
  • Don't share state between tests. Reset before each.
  • Don't test the framework. expect(typeof fn).toBe('function') adds nothing.
  • Don't snapshot-test arbitrarily. Snapshots are for stable serialized output; otherwise they just rubber-stamp regressions.

Example invocation

User: "Write tests for src/utils/slugify.ts." (file exports slugify(s: string, options?: { maxLen?: number }): string)

  1. Detect: Vitest, __tests__/ adjacent.
  2. List failure modes: empty string, all spaces, unicode ("café" → "cafe"?), trailing dashes, very long input (maxLen kicks in), already-slugified input (idempotent?), multi-byte emoji.
  3. Write __tests__/slugify.test.ts with three describes: happy path (8 cases), boundary (4 cases), invalid (2 cases).
  4. Add table-driven test for unicode normalization.
  5. Run: npm test slugify — 14/14 green.
  6. Spot during writing: slugify(" ") returns "-", which the user probably didn't intend. Flag it; ask whether to write the test for current behavior or the expected behavior.

See also

  • code-auditor — to find the untested edge cases before you start writing
  • browser-qa — when the behavior under test is a user flow, not a function
  • refactor-master — if you need to refactor to make the code testable

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.