tdd
Write unit and integration tests for an existing file, class, or feature. Auto-invoke when the user says "write tests for", "add test coverage", "test this service", or "what's not tested here".
Files1
View on GitHubSKILL.mdskills/tdd/SKILL.md
GitHubTDD Skill
Write tests for the specified file or feature. Tests test behaviour, not implementation.
Input
$ARGUMENTS— target file path + optional scope (unit,integration,both)- Default scope if not specified:
both
Step 1 — Read and Understand
- Read the target file fully
- Identify: what are the public methods / exported functions?
- Identify: what are the dependencies? (for mocking in unit tests)
- Identify: what are the expected behaviours for each public method?
Step 2 — Detect the Stack
Check for package.json (Node.js/Jest/Vitest), go.mod (Go/testify), composer.json (PHP/Pest or PHPUnit).
Read the matching process file for test conventions:
- Node.js →
@~/.claude/process/node_process.md - Golang →
@~/.claude/process/go_process.md - Laravel →
@~/.claude/process/php_process.md
Step 3 — Plan the Test Cases
Before writing any code, list the test cases in this format:
[MethodName]
✓ should return X when Y
✓ should throw NotFoundError when Z is missing
✓ should call repository.save() once when input is valid
✓ should not call repository.save() when validation fails
Present this plan and wait for approval before writing tests.
Step 4 — Write Unit Tests
Rules:
- Mock ALL external dependencies (repositories, HTTP clients, queues, etc.)
- Test one behaviour per test case — one logical assertion focus
- Test naming:
it('should <behaviour> when <condition>') - Set up mocks in
beforeEach, reset inafterEach - Assert on outputs and observable side effects — not on internal state
- Test both happy path AND all meaningful error/edge cases
Node.js (Jest/Vitest)
// src/services/__tests__/OrderService.test.ts
describe('OrderService', () => {
let sut: OrderService;
let mockRepo: jest.Mocked<IOrderRepository>;
beforeEach(() => {
mockRepo = { findById: jest.fn(), save: jest.fn() };
sut = new OrderService(mockRepo);
});
it('should return order when id exists', async () => { ... });
it('should throw OrderNotFoundError when id does not exist', async () => { ... });
});
Go (testify)
func TestOrderService(t *testing.T) {
tests := []struct {
name string
input string
want *Order
wantErr error
}{
{"found", "1", &Order{ID: "1"}, nil},
{"not found", "99", nil, ErrNotFound},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { ... })
}
}
PHP (Pest)
it('should return order when id exists', function () {
$repo = Mockery::mock(OrderRepositoryInterface::class);
$repo->shouldReceive('findById')->with(1)->andReturn(new Order(id: 1));
$sut = new OrderService($repo);
expect($sut->get(1))->toBeInstanceOf(Order::class);
});
Step 5 — Write Integration Tests (if in scope)
- Use a real test database (never mock the DB in integration tests)
- Seed required data in
beforeEach/setUp - Clean up in
afterEach/tearDown - Test the repository layer directly — does the data actually persist and query correctly?
Output
- Write test files to the correct location per stack conventions
- Print a summary: number of test cases written, coverage of public methods, any behaviours that could not be tested without additional refactoring (and why)