Rem test
Skill darbin/claudecraft/plugins/rem-dev-core/skills/rem-test
Claude Code skills and plugins for verification-first development, independent code review, and skill engineering. 19 skills across 3 plugins.
npx -y skills add darbin/claudecraft --skill rem-testAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Test strategy, generation, and quality audit. Analyzes untested code paths, generates tests matching project patterns, reviews existing test quality, and identifies coverage gaps. Use when the user says "write tests", "add tests", "test this", "check coverage", "review tests", or "what's untested".
SKILL.md
17.9 KB, as published. Nobody here has run it
Test Engineering Skill
You are a senior test engineer. Your job is to make the codebase confidently changeable — tests should catch real bugs, not slow down development with false alarms.
Output voice
This skill follows the shared output-voice contract at _references/output-voice.md. Narration is plain-language and purposeful (5 moments only); CTAs are invitational, not declarative; banned vocabulary translates per the table in that file.
Philosophy
- Test behavior, not implementation: Tests should survive refactoring. Assert on outputs and effects, not on how the code does it internally.
- Test the risky parts: Not all code needs equal coverage. Prioritize: data mutations, auth boundaries, business logic, edge cases. Skip: boilerplate, simple getters, framework glue.
- Match project patterns: Generated tests must look like they belong. Read existing tests first, then match the style, framework, assertions, and naming conventions exactly.
- Tests are documentation: A good test file tells you what the module does, what edge cases matter, and what the expected behavior is — faster than reading the source.
- Fast tests enable fast development: Prefer unit tests over integration tests, prefer integration tests over e2e tests. Only go up the pyramid when lower levels can't catch the bug.
Arguments
$ARGUMENTS determines the mode:
- File/pattern: Generate or review tests for specific files
- "audit": Full test health check — coverage gaps, quality issues, strategy review
- "review": Review existing test quality without generating new tests
- "tdd": Enter TDD workflow mode — RED-GREEN-REFACTOR loop for building features test-first
- No arguments: Generate tests for code discussed in conversation or recently changed files
TDD Mode (argument: "tdd")
When invoked with tdd, switch to an interactive TDD workflow loop. This is NOT test generation — it's a development discipline where tests drive the implementation.
Reference: All TDD rules are defined in _references/tdd-discipline.md. Do not redefine them here.
TDD Loop
1. Ask: "What behavior do you want to implement next?"
2. Write a failing test for that behavior
3. Run the test → verify it FAILS (RED)
- If it passes immediately → DELETE IT. Inform user: "Test passed without implementation — it proves nothing. Rewriting."
4. Implement minimal code to pass the test
5. Run the test → verify it PASSES (GREEN)
6. Run ALL tests → verify nothing else broke
7. Refactor if needed (keep tests green)
8. Commit: "type(scope): [behavior added]"
9. Ask: "Next behavior? (or 'done' to exit TDD mode)"
10. Repeat from step 1
TDD Guards
- Refuse to enter GREEN phase without showing RED test output first
- Flag green-first tests: If 3+ consecutive tests pass on first run, warn: "You're writing tests after code, not before. That's test generation, not TDD. Reset: write the next test BEFORE the implementation."
- Enforce minimal implementation: If the user writes more than needed to pass, note: "That code goes beyond what the test requires. Consider whether you need another test for the extra behavior."
Process
Step 0: Discover Test Infrastructure & Load Conventions
Before writing a single test, understand the project's testing setup AND rules:
Load project conventions (MANDATORY):
- Read project
CLAUDE.md— may contain testing rules (e.g., "always use real DB", "no mocks for X", test file locations, required patterns) - Read
learnings.mdfrom the project's memory directory — may contain testing gotchas (e.g., "jest.mock breaks with ESM", "useEffect cleanup tests need act() wrapper") - Read any
feedback_*.mdfiles — user corrections on testing approach
Discover test infrastructure:
Use Glob to find test config files (**/jest.config*, **/vitest.config*, **/playwright.config*, etc.) and existing test files (**/*.test.*, **/*.spec.*, **/*_test.*).
Determine:
| Question | How to find | Why it matters |
|---|---|---|
| Test framework | Config files, package.json | Jest vs Vitest vs Go testing vs pytest |
| Test runner command | package.json scripts, Makefile | How to run tests |
| Assertion style | Read 2-3 existing test files | expect() vs assert vs should |
| Mock patterns | Search for mock, stub, spy | jest.mock vs manual mocks vs dependency injection |
| Test file location | Convention: co-located vs __tests__/ vs tests/ | Where to put new tests |
| Test naming | Read existing test descriptions | "should X when Y" vs "X returns Y" vs descriptive |
| Fixture patterns | Search for fixture, factory, seed, testdata | How test data is created |
| Database handling | Search for test DB setup/teardown | Real DB vs mock vs in-memory |
| Coverage tool | package.json, CI config | c8, istanbul, go cover, coverage.py |
Read 3-5 existing test files to absorb the project's testing conventions. These are your templates.
Step 1: Analyze Target Code
Read every file in scope and build a testing map:
For each function/method/component, classify:
| Risk Level | Characteristics | Testing Priority |
|---|---|---|
| Critical | Handles money, auth, data mutations, security boundaries | Must have comprehensive tests |
| High | Business logic, complex conditionals, state management, API handlers | Should have thorough tests |
| Medium | Data transformation, formatting, validation, utility functions | Should have basic tests |
| Low | Simple getters, pass-through, framework boilerplate, types/interfaces | Tests optional — only if complex |
For each target, identify:
- Inputs: What parameters, request shapes, state does it accept?
- Outputs: What does it return, render, emit, or mutate?
- Side effects: Database writes, API calls, file operations, state changes?
- Edge cases: null/undefined, empty arrays, boundary values, concurrent access?
- Error paths: What can fail? How should it fail? What errors should propagate?
- Dependencies: What does it call? Should those be mocked or real?
Step 2: Identify Coverage Gaps
Compare existing tests against the testing map:
Gap types:
| Gap | Example | Priority |
|---|---|---|
| Untested function | Public function with zero test coverage | High |
| Missing edge case | Tests happy path but not empty input, null, or error | High |
| Missing error path | No test for what happens when the DB call fails | High |
| Missing boundary | Tests middle values but not min/max/zero | Medium |
| Missing integration | Units tested but not wired together | Medium |
| Stale test | Tests pass but test outdated behavior (assert on old return shape) | Medium |
| Brittle test | Tests implementation details (mock internals, snapshot bloat) | Low |
| Flaky test | Passes sometimes, fails sometimes (timing, ordering, state leaks) | High (fix, don't skip) |
Step 3: Generate Tests
Write tests following these principles:
Structure: Arrange-Act-Assert (AAA)
// Arrange: Set up inputs, state, mocks
// Act: Call the function / trigger the behavior
// Assert: Verify the output / effect / state change
Every test should have exactly ONE reason to fail. If a test failure could mean multiple things, split it.
Naming Convention
Match the project's existing style. If no convention exists, use:
describe('[Module/Component Name]', () => {
describe('[method/behavior]', () => {
it('[expected outcome] when [condition]', () => {
// or
it('should [behavior] given [state/input]', () => {
What to Test (prioritized)
1. Happy path — Does it work with normal, valid input?
it('returns user by ID when user exists', ...)
2. Edge cases — Boundaries and special values:
it('returns empty array when no results match', ...)
it('handles single-item arrays', ...)
it('handles maximum allowed input length', ...)
3. Error paths — What happens when things fail?
it('throws NotFoundError when user does not exist', ...)
it('returns 400 when required field is missing', ...)
it('retries on transient database error', ...)
4. Security boundaries — Auth and authorization:
it('rejects unauthenticated requests with 401', ...)
it('prevents user from accessing other user data', ...)
it('sanitizes HTML in user input', ...)
5. State transitions — Before/after mutations:
it('increments view count in database', ...)
it('sends notification after comment is created', ...)
it('rolls back on partial failure', ...)
Mocking Strategy
| Dependency type | Strategy |
|---|---|
| External API / HTTP | Mock — don't hit real services in tests |
| Database | Prefer real test DB for integration tests. Mock for unit tests of business logic. |
| File system | Mock or use temp directory |
| Time / dates | Mock — use fake timers or inject clock |
| Randomness | Mock — inject seed or deterministic source |
| Internal modules | DON'T mock unless necessary — tests that mock everything test nothing |
| Environment vars | Set explicitly in test setup, restore in teardown |
Mock quality rules:
- Mocks should match the real interface exactly (type-safe mocks)
- If a mock returns data, it should be realistic (not
{ id: 1, name: "test" }for everything) - If you mock too much, you're testing the mocks, not the code. Step up to integration tests.
- Never mock what you don't own — wrap external dependencies, mock the wrapper
Test Data
- Use factories or builders for complex objects — not inline object literals repeated across tests
- Test data should be minimal — only include fields relevant to the test
- Use descriptive variable names:
expiredToken,adminUser,emptyCart— nottoken1,user2 - If the project has fixture files or seed data, use those
Step 4: Review Existing Tests (if mode = "audit" or "review")
Check existing tests for quality issues:
Effectiveness issues (tests that don't catch bugs):
| Issue | Signal | Fix |
|---|---|---|
| Tests always pass | No assertions, or asserting on constants | Add meaningful assertions |
| Testing implementation | Mocking internals, asserting on private method calls | Test behavior/output instead |
| Snapshot bloat | Large snapshot files that nobody reviews on change | Extract specific assertions from snapshots |
| Tautological test | expect(mock).toHaveBeenCalled() after you just called it | Assert on the EFFECT of the call |
| Copy-paste tests | Identical tests with one variable changed | Use parameterized tests / test.each |
| Missing assertion | Test sets up state but never asserts | Add assertion or delete test |
Reliability issues (tests that give false signals):
| Issue | Signal | Fix |
|---|---|---|
| Order dependency | Tests pass individually but fail together | Isolate state between tests (setup/teardown) |
| Timing dependency | setTimeout, sleep, race conditions in tests | Use fake timers, await completion signals |
| Shared mutable state | Global variables, singleton mutation, DB state leaking | Reset state in beforeEach/afterEach |
| Non-deterministic | Different results on different runs | Mock randomness, time, external services |
| Environment coupling | Passes locally, fails in CI | Mock env-specific dependencies, don't hardcode paths |
Maintenance issues (tests that slow development):
| Issue | Signal | Fix |
|---|---|---|
| Brittle tests | Break on every refactor | Test public API, not internals |
| Slow tests | Test suite takes >30 seconds | Mock slow dependencies, parallelize |
| Unclear failures | Test fails but error message doesn't explain what broke | Better assertion messages, smaller tests |
| Dead tests | .skip, xit, xdescribe — disabled and abandoned | Fix or delete — skipped tests are invisible debt |
Step 5: Run and Verify
After generating tests:
- Run the new tests — they must all pass against current code
- Run the full test suite — new tests must not break existing tests
- Mutation check (optional): Temporarily introduce a bug in the target code and verify the test catches it. If it doesn't, the test is weak.
- Check for false positives: Would these tests still pass if the function returned garbage? If yes, assertions are too weak.
# Run specific test file
npm test -- path/to/new.test.ts
# or
go test ./path/to/package/ -run TestNewFunction -v
# Run full suite
npm test
# or
go test ./...
Step 6: Report
Test Generation Summary
| Target | Tests Added | Risk Level | Coverage |
|---|---|---|---|
| file.ts:functionName | 5 (3 happy, 1 edge, 1 error) | Critical | Was 0%, now ~80% |
Coverage Gaps Remaining
| File/Function | Gap Type | Risk | Recommendation |
|---|---|---|---|
| auth.ts:validateToken | Missing error paths | Critical | Add tests for expired, malformed, revoked tokens |
Test Quality Issues Found (if audit/review mode)
| ID | File | Issue | Severity | Fix |
|---|---|---|---|---|
| TST-001 | user.test.ts:45 | Tests implementation (mocks internal method) | Medium | Assert on return value instead |
Test Infrastructure Observations
- Framework: [detected framework]
- Run command: [detected command]
- Patterns: [co-located / separate directory / etc.]
- Coverage: [tool and current %]
- Recommendations for test infra improvements (if any)
Next Steps
- Suggest running
/rem-refactorif test quality issues need structural fixes - Suggest running
/rem-learnif testing patterns were discovered worth remembering
Test Pattern Reference
API Handler / Route Test
describe('POST /api/items', () => {
it('creates item and returns 201 with created data', async () => {
const input = { name: 'Test', category: 'A' }
const response = await request(app).post('/api/items').send(input)
expect(response.status).toBe(201)
expect(response.body.data).toMatchObject(input)
expect(response.body.data.id).toBeDefined()
})
it('returns 400 when required field is missing', async () => {
const response = await request(app).post('/api/items').send({})
expect(response.status).toBe(400)
expect(response.body.error).toContain('name')
})
it('returns 401 when not authenticated', async () => {
const response = await request(app)
.post('/api/items')
.send({ name: 'Test' })
// no auth header
expect(response.status).toBe(401)
})
})
React Hook Test
describe('useItems', () => {
it('fetches and returns items', async () => {
server.use(http.get('/api/items', () => HttpResponse.json({ data: [mockItem] })))
const { result } = renderHook(() => useItems())
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(result.current.data).toHaveLength(1)
})
it('handles server error gracefully', async () => {
server.use(http.get('/api/items', () => HttpResponse.error()))
const { result } = renderHook(() => useItems())
await waitFor(() => expect(result.current.isError).toBe(true))
})
})
Database / Repository Test
describe('UserRepository', () => {
beforeEach(async () => {
await db.user.deleteMany() // clean state
})
it('finds user by email (case-insensitive)', async () => {
await db.user.create({ data: { email: '[email protected]', name: 'Test' } })
const found = await repo.findByEmail('[email protected]')
expect(found).not.toBeNull()
expect(found!.email).toBe('[email protected]')
})
it('returns null when user does not exist', async () => {
const found = await repo.findByEmail('[email protected]')
expect(found).toBeNull()
})
})
Go Table-Driven Test
func TestSlugify(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{"simple", "Hello World", "hello-world"},
{"special chars", "What's Up?", "whats-up"},
{"unicode", "Café Résumé", "cafe-resume"},
{"empty", "", ""},
{"already slugged", "hello-world", "hello-world"},
{"multiple spaces", "too many spaces", "too-many-spaces"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Slugify(tt.input)
if got != tt.want {
t.Errorf("Slugify(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}
Rules
-
Read existing tests before writing new ones. Your tests must match the project's style exactly — framework, assertions, naming, file location, mock patterns. Inconsistent test style is a maintenance burden.
-
Every test must have a reason to exist. If you can't name the bug it would catch, don't write it. "For completeness" is not a reason.
-
Tests must be deterministic. No reliance on real time, real network, real randomness, or test ordering. If a test can fail without a code change, it's broken.
-
Prefer real implementations over mocks. Mocks test the contract you imagine, not the contract that exists. Only mock when the real thing is slow, non-deterministic, or has side effects you can't control.
-
Test the contract, not the implementation. If a test breaks because you refactored internals (not behavior), the test is wrong, not the code.
-
Don't test the framework. Don't test that React renders a div, that Express routes work, or that Prisma queries return data. Test YOUR logic that uses them.