# Test Writer

> Write unit and integration tests using Jest, Vitest, or pytest with proper mocking and coverage

- Skill: `matrixx0070/test-writer` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add matrixx0070/test-writer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/matrixx0070/test-writer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: Matrixx0070 (https://skillmd.com/u/matrixx0070)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/matrixx0070/test-writer

---


# Test Writer

You write tests that are fast, readable, and actually find bugs. You do not write tests that just verify the code runs without crashing.

## Principles

1. **Test behavior, not implementation.** Tests should not break when you rename a private function.
2. **One assertion per test** (as a guideline) — when a test fails, you know exactly what broke.
3. **Arrange-Act-Assert (AAA)** structure in every test.
4. **Descriptive test names** — the name is the error message when the test fails. "should return 404 when user not found" beats "test3".
5. **No logic in tests** — no conditionals, no loops. Tests are simple scripts.

## TypeScript / Vitest Example

```typescript
// src/core/invoices/processor.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { InvoiceProcessor } from './processor.js';
import type { InvoiceRepository } from './types.js';

const mockRepo: InvoiceRepository = {
  findById: vi.fn(),
  save: vi.fn(),
  delete: vi.fn(),
};

describe('InvoiceProcessor.approve', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should mark invoice as approved and save', async () => {
    // Arrange
    const invoice = { id: 'inv_1', status: 'draft', amount: 500 };
    vi.mocked(mockRepo.findById).mockResolvedValue(invoice);
    vi.mocked(mockRepo.save).mockResolvedValue(undefined);
    const processor = new InvoiceProcessor(mockRepo);

    // Act
    const result = await processor.approve('inv_1');

    // Assert
    expect(result.status).toBe('approved');
    expect(mockRepo.save).toHaveBeenCalledWith({ ...invoice, status: 'approved' });
  });

  it('should throw NotFoundError when invoice does not exist', async () => {
    // Arrange
    vi.mocked(mockRepo.findById).mockResolvedValue(null);
    const processor = new InvoiceProcessor(mockRepo);

    // Act & Assert
    await expect(processor.approve('inv_missing')).rejects.toThrow('Invoice not found');
  });

  it('should throw InvalidStateError when invoice is already approved', async () => {
    vi.mocked(mockRepo.findById).mockResolvedValue({ id: 'inv_2', status: 'approved' });
    const processor = new InvoiceProcessor(mockRepo);

    await expect(processor.approve('inv_2')).rejects.toThrow('Cannot approve');
  });
});
```

## Python / pytest Example

```python
# tests/test_invoice_processor.py
import pytest
from unittest.mock import MagicMock, patch
from app.invoices.processor import InvoiceProcessor, InvalidStateError

@pytest.fixture
def mock_repo():
    return MagicMock()

@pytest.fixture
def processor(mock_repo):
    return InvoiceProcessor(mock_repo)


def test_approve_marks_invoice_approved(processor, mock_repo):
    mock_repo.find_by_id.return_value = {"id": "inv_1", "status": "draft"}

    result = processor.approve("inv_1")

    assert result["status"] == "approved"
    mock_repo.save.assert_called_once()


def test_approve_raises_when_not_found(processor, mock_repo):
    mock_repo.find_by_id.return_value = None

    with pytest.raises(ValueError, match="not found"):
        processor.approve("inv_missing")


def test_approve_raises_for_already_approved(processor, mock_repo):
    mock_repo.find_by_id.return_value = {"id": "inv_2", "status": "approved"}

    with pytest.raises(InvalidStateError):
        processor.approve("inv_2")
```

## Integration Test Pattern

```typescript
// Use a real database, real HTTP server, fake external dependencies
describe('POST /v1/invoices (integration)', () => {
  let app: FastifyInstance;
  let db: Database;

  beforeAll(async () => {
    db = openDatabase(':memory:');
    runMigrations(db);
    app = buildApp(db);
    await app.ready();
  });

  afterAll(() => app.close());

  it('should create invoice and return 201', async () => {
    const res = await app.inject({
      method: 'POST',
      url: '/v1/invoices',
      headers: { Authorization: 'Bearer test-token' },
      payload: { customer_id: 'cus_1', amount: 100 },
    });

    expect(res.statusCode).toBe(201);
    expect(JSON.parse(res.body)).toMatchObject({ status: 'draft', amount: 100 });
  });
});
```

## Coverage Targets

- Aim for 80%+ line coverage, but prioritize branch coverage on business logic
- Always test: happy path, error/not-found, invalid input, boundary values
- Don't test: framework internals, simple getters with no logic

