# Skill: test writer

## Description

Generate tests for a given source file. Detects the programming
language, finds the project's testing framework, studies existing
test files for style patterns, and writes tests that match.

Invoke this skill with: /write-tests <file-path>
Or by asking: "Write tests for src/utils/parser.ts"

The file path argument is required. If not provided, ask for it.

### Returns

A test file written to disk at the location matching the project's test
convention (sibling `.test.ts`, `__tests__/` folder, or a separate `tests/`
tree, whichever the existing tests use). Plus structured metadata:
the test framework detected, the source file path, the test file path,
the number of test cases generated, and the list of exports covered.
A composing skill (a CI runner, a coverage reporter) can read these.

## When to use

- The user asks to write tests for a specific file
- The user runs `/write-tests <path>`
- A new file was added without tests and the user wants coverage
- An existing file's behavior changed and the user wants to extend the test suite

## When not to use

- The file has no testable exports (a pure type-definition file, a config object with no logic). The skill detects this in step 1 and stops, but it's worth declining at invocation time when the answer is obvious.
- The project has no detectable test framework and the user hasn't picked one. Generating tests in a framework the project doesn't use just creates dead code.
- The source file's behavior depends on external systems (a real database, a third-party API) and the user hasn't decided on a mocking strategy. Tests written without that decision will need to be rewritten.
- The user wants integration or end-to-end tests, not unit tests. This skill writes unit tests by default.

## Steps

### 1. Read the source file

Read the full contents of the specified file. Identify:

- The programming language (from the file extension and content)
- Every exported function, class, or method
- Every public method on exported classes
- The function signatures: parameter names, types, return types
- Any dependencies the functions import or require

If the file doesn't exist, tell the developer and stop.
If the file has no testable exports (e.g., it's a type definition
file or a config file with no logic), explain why and stop.

### 2. Detect the testing framework

Look for the project's testing setup. Check these locations in order:

**For JavaScript/TypeScript:**

- `vitest.config.*` or `vite.config.*` with test config → Vitest
- `jest.config.*` or `"jest"` in package.json → Jest
- `package.json` dependencies containing `mocha` → Mocha + Chai
- If none found, default to Vitest

**For Python:**

- `pytest.ini`, `pyproject.toml` with `[tool.pytest]`, or
  `conftest.py` in the project → pytest
- If none found, default to pytest

**For Go:**

- Go uses the standard `testing` package. No detection needed.

**For Rust:**

- Rust uses built-in `#[cfg(test)]` modules. No detection needed.

**For other languages:**

- Check for common test runner config files.
- If you can't determine the framework, ask the developer.

Also note the test runner command so you can run the tests later:

- Vitest: `npx vitest run`
- Jest: `npx jest`
- pytest: `python -m pytest` or `pytest`
- Go: `go test ./...`
- Rust: `cargo test`

### 3. Find existing test patterns

Search the project for existing test files. Look in:

- `__tests__/` directories
- `test/` or `tests/` directories
- Files named `*.test.*` or `*.spec.*` colocated with source
- For Go: `*_test.go` files
- For Rust: `mod tests` blocks in source files

Read 2-3 existing test files (preferably ones testing similar code)
and note the project's testing patterns:

- **Import style:** How do they import the test framework and the
  code under test? Named imports, default imports, relative paths?
- **Describe/it structure vs flat test functions:** Do they nest
  describes? Use `it` or `test`?
- **Naming conventions:** "should do X", "does X when Y",
  "test_function_name_condition", etc.
- **Setup patterns:** beforeEach/afterEach, test fixtures, factory
  functions, mocks?
- **Assertion style:** expect().toBe(), assert.equal(), t.Equal()?
- **Mock patterns:** Do they use vi.mock, jest.mock, unittest.mock,
  manual stubs?
- **File naming:** `foo.test.ts` next to `foo.ts`? Or
  `__tests__/foo.test.ts`?
- **File location:** Where do test files live relative to source?

If no existing tests are found, use the testing framework's
standard conventions.

### 4. Plan the tests

For each exported function or public method, plan test cases:

**Happy path tests (required for every function):**

- Call with typical, valid inputs
- Verify the expected return value

**Edge case tests (add where applicable):**

- Empty inputs (empty string, empty array, null, undefined)
- Boundary values (zero, negative numbers, max int, very long
  strings)
- Single-element collections
- Inputs at type boundaries

**Error path tests (add where applicable):**

- Invalid inputs that should throw or return errors
- Missing required parameters
- Inputs that violate documented constraints

**Integration-style tests (only if the function has dependencies):**

- Mock external dependencies (database, HTTP, file system)
- Test that the function calls its dependencies correctly
- Test behavior when dependencies fail

Do not generate tests for:

- Private/internal functions (unless the language exposes them
  to the test module, like Rust's `mod tests`)
- Trivial getters/setters with no logic
- Functions that only re-export from another module

### 5. Write the tests

Generate the complete test file. Follow these rules:

- Match the import style, naming conventions, describe structure,
  and assertion patterns you found in step 3.
- Put the test file in the same location pattern as existing tests
  (colocated or in a test directory).
- Include a brief comment at the top: `// Tests for <source-file>`
- Group tests by function: one describe block (or equivalent) per
  function being tested.
- Give each test a clear name that states the expected behavior,
  not the implementation detail.
- Keep tests independent. No test should depend on another test's
  state or execution order.
- Use realistic test data, not generic placeholders like "foo" and
  "bar" (unless testing string-agnostic behavior).
- For mocked dependencies, set up mocks in the smallest scope
  possible (per-test, not per-file, unless the pattern in step 3
  says otherwise).

Write the file to disk at the appropriate path.

### 6. Run the tests

Run the test suite using the command identified in step 2. Run
only the new test file, not the entire suite:

- Vitest: `npx vitest run <test-file>`
- Jest: `npx jest <test-file>`
- pytest: `python -m pytest <test-file> -v`
- Go: `go test -run <TestFunctionPattern> <package>`
- Rust: `cargo test <test-name-pattern>`

### 7. Fix failures

If any tests fail:

- Read the error output carefully.
- Determine whether the test is wrong (bad assertion, wrong mock
  setup) or the source code has a bug.
- If the test is wrong, fix the test and re-run. Repeat up to
  3 times.
- If the source code appears to have a bug, do NOT fix the source.
  Instead, add a comment to the failing test:
  `// FIXME: This test reveals a potential bug: [description]`
  and mark the test as skipped/pending with an explanation.

After all tests pass (or are explicitly skipped with explanations),
show the developer the results.

### 8. Report results

Show the developer:

```
## Test generation results

**Source file:** <path>
**Test file:** <path>
**Tests written:** <count>
**Tests passing:** <count>
**Tests skipped:** <count> (with reasons if any)

### Coverage

- function_name: [X tests]: happy path, edge cases, error handling
- another_function: [Y tests]: happy path, boundary values
...

### Notes

[Any observations: functions that were hard to test, dependencies
that needed complex mocking, potential bugs found, suggestions
for improving testability]
```

## Rules

- Always match the existing project style. Your tests should look
  like a human on the team wrote them.
- Never modify the source file being tested. Tests adapt to the
  code, not the other way around.
- If you can't determine the testing framework, ask. Do not guess
  wrong and generate incompatible tests.
- Run the tests before reporting success. "I wrote tests" is not
  the same as "I wrote tests that pass."
- Keep test files focused. One test file per source file. Don't
  combine tests for multiple source files.
