Test write
Skill kennyolofsson23-netizen/claude-code-config/skills/test-write
The most comprehensive Claude Code setup — 29 skills, 9 plugins, 13 hooks, 8 agents, 8 commands. Self-documenting, self-improving, CLI-first. Includes self-interview prompt for personalization.
npx -y skills add kennyolofsson23-netizen/claude-code-config --skill test-writeAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 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
Write tests for the Travmaskinen codebase — backend (pytest, async, FastAPI), frontend (Vitest + React Testing Library), or E2E (Playwright). Use this skill whenever writing new tests, adding test coverage, implementing TDD red-green-refactor cycles, or when the user says "write tests", "add tests", "test this", "cover this", "TDD", or asks for test coverage on any module. Also triggers when implementing new features that need tests first (TDD flow).
SKILL.md
8.2 KB, as published. Nobody here has run it
Test Writer — Travmaskinen
Write tests that match the project's established conventions exactly. The codebase has 2,032 backend tests and 101 frontend tests — new tests must be indistinguishable from existing ones in style.
Decision: Which Layer?
| Target | Framework | Config | Run Command |
|---|---|---|---|
Python module (backend/**/*.py) | pytest + asyncio | pyproject.toml | pytest backend/tests/ -v --tb=short |
React component (apps/web/**/*.tsx) | Vitest + RTL | apps/web/vitest.config.ts | pnpm --filter web test |
| User flow (multi-page) | Playwright | apps/web/playwright.config.ts | pnpm --filter web test:e2e |
Coverage commands:
- Backend:
pytest backend/tests/ --cov=backend --cov-report=term-missing - Frontend:
pnpm --filter web test:coverage
TDD Flow (Always)
- Red: Write the test first — it MUST fail
- Green: Write minimal code to make it pass
- Refactor: Clean up without changing behavior
- Run the full suite to confirm no regressions
Backend Tests (pytest)
File Placement
backend/tests/
├── unit/ # Module-level logic, mocked dependencies
├── integration/ # Full endpoint tests via TestClient
└── ml/ # Model invariants, feature parity
Name: test_{module_name}.py — mirrors the source file.
Shared Fixtures (from conftest.py)
Use these — don't recreate them:
fixture_game(session): Full ATG game fromscripts/fixtures/v85_2026-03-07.jsonfixture_race(function): First race from fixture_gamefixture_horses(function): Horse entries from first racemock_intel(function):MagicMockofV85DatabaseIntelligencewith sensible zero-data defaults for all 30+ methodsclient(function):FastAPI TestClientwith mocked DB and settings
Patterns
Async endpoint/service test
@pytest.mark.asyncio
async def test_cache_miss_calls_predictor():
"""When cache is empty, predictor.predict() is called."""
import backend.api.predict as mod
mock_pred = MagicMock()
mock_pred._loaded = True
mock_pred.predict = AsyncMock(return_value=[{"horse_id": 1, "prob": 0.5}])
with patch.object(mod, "predictor", mock_pred), \
patch.object(mod, "cache_get", new_callable=AsyncMock, return_value=None), \
patch.object(mod, "cache_set", new_callable=AsyncMock):
result = await mod.predict_race("2026-03-15_1_1")
mock_pred.predict.assert_called_once()
assert result[0]["prob"] == 0.5
Pydantic validation
def test_invalid_race_id_rejected():
from pydantic import ValidationError
with pytest.raises(ValidationError):
PredictRequest(race_id="bad-id")
Integration endpoint
def test_endpoint_returns_200(client):
resp = client.get("/api/v1/races/today")
assert resp.status_code == 200
assert "legs" in resp.json()
ML invariant
def test_probabilities_sum_to_one(fixture_race, mock_intel):
probs = predictor.predict(fixture_race, mock_intel)
total = sum(p["win_probability"] for p in probs)
assert abs(total - 1.0) < 0.01, f"Probabilities sum to {total}, expected ~1.0"
Rules
asyncio_mode = "auto"— no need for@pytest.mark.asyncioon fixtures, but DO use it on async test functions- Use
AsyncMockfor any async return value - Use
patch.object(module, "name")— neverpatch("string.path")(brittle) - Test BOTH success AND failure paths (cache hit/miss, model loaded/not, 200/404/500)
fail_under = 100in pyproject.toml — every line must be covered or marked# pragma: no cover- Exclude lines:
if TYPE_CHECKING:,if __name__ == "__main__":,raise NotImplementedError
Frontend Tests (Vitest + RTL)
File Placement
Tests live next to source: ComponentName.test.tsx beside ComponentName.tsx.
Test Utilities (from lib/test-utils.tsx)
Use these factories — don't create ad-hoc objects:
createHorse(overrides?)— fullBackendHorsewith sensible defaultscreateScratchedHorse(overrides?)— scratched variantcreateFreeSession()/createProSession()/createAdminSession()— auth sessionscreateUnauthenticatedSession()— no auth
Required Mocks
Most components need these at the top of the file:
vi.mock('next/navigation', () => ({
useRouter: () => ({ push: vi.fn(), back: vi.fn(), forward: vi.fn() }),
useSearchParams: () => new URLSearchParams(),
usePathname: () => '/',
}))
vi.mock('next/link', () => ({
default: ({ children, href, ...props }: any) => <a href={href} {...props}>{children}</a>,
}))
Patterns
Component render + content assertion
it('renders horse name', () => {
const horse = createHorse({ horse: { name: 'Elitlansen', id: 101 } })
render(<HorseCard horse={horse} />)
expect(screen.getByText('Elitlansen')).toBeInTheDocument()
})
Negative assertion (element NOT present)
it('hides badge when scratched', () => {
const horse = createScratchedHorse()
render(<HorseCard horse={horse} />)
expect(screen.queryByText('FAVORIT')).not.toBeInTheDocument()
})
User interaction
it('calls onToggle when clicked', () => {
const onToggle = vi.fn()
render(<HorseCard horse={createHorse()} onToggle={onToggle} />)
fireEvent.click(screen.getByText('Elitlansen').closest('[class*="rounded-lg"]')!)
expect(onToggle).toHaveBeenCalledTimes(1)
})
API/fetch mocking
beforeEach(() => { vi.useFakeTimers() })
afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks() })
it('fetches data with auth header', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ data: [] }),
}))
await api.get('/api/v1/races', { token: 'jwt-123' })
expect(fetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
headers: expect.objectContaining({ Authorization: 'Bearer jwt-123' }),
}),
)
})
Rules
vi.mock()calls must be at file top (before imports that use the mocked module)- Always
vi.restoreAllMocks()inafterEach - Use
screen.getByText()/screen.getByRole()— nevercontainer.querySelector()(fragile) - Use
queryByText()for "should NOT exist" — returns null instead of throwing - Use
expect.objectContaining()for partial object matching - Import alias:
@/lib/...maps to./lib/...
E2E Tests (Playwright)
File Placement
apps/web/e2e/{feature}.spec.ts
Patterns
import { test, expect } from '@playwright/test'
test.describe('Game page interactions', () => {
test('loads V85 game with predictions', async ({ page }) => {
await page.goto('/spel/V85')
await expect(page.locator('h1')).toBeVisible()
await expect(page).toHaveURL(/spel\/V85/)
})
test('navigates between legs', async ({ page }) => {
await page.goto('/spel/V85')
const tab = page.getByRole('tab', { name: 'Avd 2' })
await tab.click()
await expect(tab).toHaveAttribute('aria-selected', 'true')
})
})
Rules
- Tests run on 3 browsers: chromium, firefox, mobile-chrome
baseURL = http://localhost:3000— dev server auto-starts locally- Use
page.locator()for CSS,page.getByRole()for semantic elements - Always
awaitinteractions and assertions - Traces on first retry, screenshots only on failure
Checklist Before Done
- All new tests pass: run the relevant suite
- No existing tests broken: run the full suite
- Coverage check: run with
--cov/test:coverage - Test names are descriptive:
test_cache_miss_returns_fresh_predictionnottest_1 - Both happy path AND error path covered
- Factories/fixtures used (no inline 50-line objects)