Agent skill
policyengine-test-writing
This skill should be used when writing unit tests, integration tests, or test fixtures for PolicyEngine frontend apps, APIs, SDKs, and standalone tools. NOT for country model packages (policyengine-us, policyengine-uk, etc.) — those use YAML-based tests with their own conventions. Covers the Given-When-Then naming convention, fixture extraction, edge case coverage, and the rule that only modified test files should be run. Triggers: "write tests", "add tests", "unit test", "test file", "test coverage", "write a test for", "test this function", "test this component", "given when then", "test fixtures", "mock setup", "edge cases", "test naming", "test convention"
Install this agent skill to your Project
npx add-skill https://github.com/PolicyEngine/policyengine-claude/tree/main/skills/technical-patterns/policyengine-test-writing-skill
SKILL.md
PolicyEngine Test Writing
Standard conventions for writing tests in PolicyEngine frontend apps, APIs, SDKs, and standalone tools. These rules apply to every language and framework (Vitest, pytest, etc.) unless a project-specific override exists.
Country model packages — use different conventions
Do NOT apply this skill to country model packages (policyengine-us, policyengine-uk,
policyengine-canada, etc.). Those repos use YAML-based tests with entirely different structure,
naming, and tooling. For country packages, use these instead:
policyengine-testing-patterns-skill(skills/technical-patterns/policyengine-testing-patterns-skill/SKILL.md) — YAML test structure, naming conventions (variable_name.yaml,integration.yaml), period handling, error margins, and quality standardstest-creatoragent (agents/country-models/test-creator.md) — Automated agent that creates comprehensive YAML integration tests for government benefit program implementations
Country model tests are .yaml files that live alongside the variables they test, not .test.ts or
.test.py files in a separate tests/ directory.
Core Principles
1. Given-When-Then Naming
Every test name follows the pattern test__given_X_condition__then_Y_occurs:
// TypeScript / Vitest
test("test__given_valid_income__then_tax_is_calculated", () => { ... });
test("test__given_negative_income__then_error_is_thrown", () => { ... });
test("test__given_zero_children__then_ctc_is_zero", () => { ... });
# Python / pytest
def test__given_valid_income__then_tax_is_calculated():
...
def test__given_negative_income__then_error_is_thrown():
...
Inside the test body, organize code into three clearly commented sections:
test("test__given_user_clicks_submit__then_form_is_submitted", async () => {
// Given
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<Form onSubmit={onSubmit} />);
// When
await user.click(screen.getByRole("button", { name: /submit/i }));
// Then
expect(onSubmit).toHaveBeenCalledOnce();
});
2. One Test File Per Source File
Each source file gets exactly one corresponding test file named test_FILENAME:
| Source file | Test file |
|---|---|
utils/formatCurrency.ts |
tests/unit/utils/test_formatCurrency.test.ts |
components/MetricCard.tsx |
tests/unit/components/test_MetricCard.test.tsx |
lib/api/client.ts |
tests/unit/lib/api/test_client.test.ts |
services/simulation.py |
tests/unit/services/test_simulation.py |
The test file mirrors the source directory structure under a tests/ root.
3. Fixtures Live Separately
All mocks, setup code, patches, constants, and test data must be extracted to a fixture file with the same name in a fixtures/ directory:
tests/
├── fixtures/
│ ├── utils/
│ │ └── test_formatCurrency.ts ← mocks, constants, helpers
│ ├── components/
│ │ └── test_MetricCard.ts
│ └── lib/
│ └── api/
│ └── test_client.ts
├── unit/
│ ├── utils/
│ │ └── test_formatCurrency.test.ts ← imports from fixtures
│ ├── components/
│ │ └── test_MetricCard.test.tsx
│ └── lib/
│ └── api/
│ └── test_client.test.ts
What goes in fixtures:
- Mock data objects and factory functions
- Descriptive constants (no magic numbers in tests)
vi.fn()/MagicMocksetup helpers- Patch targets and mock response builders
- Shared
beforeEach/afterEachsetup functions
What stays in the test file:
describe/testblocks- The Given-When-Then logic
expect/assertstatements
Import everything from the fixture:
import {
VALID_HOUSEHOLD,
EMPTY_HOUSEHOLD,
mockApiSuccess,
mockApiError,
EXPECTED_TAX_AMOUNT,
} from "@/tests/fixtures/lib/api/test_client";
4. Test Edge Cases and Failure Paths
Every test file must cover, at minimum:
- Happy path: Normal inputs produce expected outputs
- Boundary values: Zero, empty string, empty array, min/max values
- Error cases: Invalid inputs, network failures, missing data
- Null/undefined: What happens with missing or nullable fields
- Type coercion traps: String "0" vs number 0, empty object vs null
Structure the describe block to make coverage obvious:
describe("calculateTax", () => {
// Happy path
test("test__given_valid_income__then_correct_tax_returned", () => { ... });
test("test__given_income_at_bracket_boundary__then_correct_bracket_applied", () => { ... });
// Edge cases
test("test__given_zero_income__then_zero_tax", () => { ... });
test("test__given_negative_income__then_throws_error", () => { ... });
// Error handling
test("test__given_api_timeout__then_error_propagated", () => { ... });
test("test__given_malformed_response__then_fallback_used", () => { ... });
});
5. Run Only What Changed
After writing or modifying test files, run only those specific tests — never the entire suite:
# TypeScript / Vitest — run specific test file(s)
bunx vitest run tests/unit/utils/test_formatCurrency.test.ts
# Python / pytest — run specific test file(s)
pytest tests/unit/variables/test_income.py -v
After tests pass, run formatters and typecheckers only on modified files:
# TypeScript — typecheck and lint only changed files
bunx tsc --noEmit
bunx eslint tests/unit/utils/test_formatCurrency.test.ts tests/fixtures/utils/test_formatCurrency.ts
# Python — format and lint only changed files
black tests/unit/variables/test_income.py tests/fixtures/variables/test_income.py
ruff check tests/unit/variables/test_income.py tests/fixtures/variables/test_income.py
Never run the full test suite or full linter unless explicitly asked. Large codebases take minutes to lint/test; running everything wastes time and produces noise unrelated to the changes.
Framework-Specific Notes
Vitest (TypeScript / React)
import { describe, test, expect, vi, beforeEach } from "vitest";
- Use
vi.fn()for mocks,vi.mock()for module mocks - Use
vi.clearAllMocks()inbeforeEach - For React components, prefer accessibility selectors (
getByRole,getByLabelText) over test IDs - Use
userEvent.setup()for user interactions (notfireEvent) - Use
waitForfor async state updates
pytest (Python)
import pytest
from unittest.mock import MagicMock, patch
- Use
@pytest.fixturefor setup, import from fixture files - Use
@pytest.mark.parametrizefor data-driven tests - Use
pytest.raises(ExceptionType)for error assertions - Mark slow tests with
@pytest.mark.slow
What to Test
- Public API surface (exported functions, component props, class methods)
- State transitions and side effects
- Data transformations and calculations
- Error handling and recovery paths
- Boundary conditions and edge cases
What NOT to Test
- Third-party library internals (Recharts rendering, Mantine components, pandas operations)
- Private implementation details that may change
- CSS/styling (unless testing conditional class application)
- Simple pass-through getters with no logic
Detailed Reference
For fixture best practices, mock patterns, and accessibility selector priority, consult:
references/fixture-patterns.md— Comprehensive fixture organization and mock examples
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
policyengine-healthcare
Healthcare program modeling in PolicyEngine-US — Medicaid, ACA marketplace, CHIP, and Medicare. Covers encoding rules, running analyses, and navigating the unique complexity of US healthcare programs. Triggers: "healthcare", "health insurance", "Medicaid", "ACA", "CHIP", "Medicare", "marketplace", "premium tax credit", "APTC", "PTC", "SLCSP", "benchmark plan", "rating area", "age curve", "family tier", "coverage gap", "Medicaid expansion", "MAGI", "medicaid_magi", "aca_magi", "medicaid_income_level", "medicaid_category", "enrollment", "takeup", "take-up", "per capita", "CSR", "cost sharing", "insurance premium", "second lowest silver", "required contribution percentage", "42 CFR", "IRC 36B", "categorical eligibility", "expansion adult", "healthcare reform", "healthcare analysis", "health policy".
policyengine-us
ALWAYS LOAD THIS SKILL FIRST before writing any PolicyEngine-US code. Contains the correct API patterns for household calculations and population simulations using the new policyengine package. Covers US federal and state taxes/benefits. Triggers: "what would", "how much would a", "benefit be", "eligible for", "qualify for", "single parent", "married couple", "family of", "household of", "if they earn", "earning $", "making $", "calculate benefits", "calculate taxes", "benefit for a", "what would I get", "what is the maximum", "what is the rate", "poverty line", "income limit", "benefit amount", "maximum benefit", "compare states", "TANF", "SNAP", "EITC", "CTC", "SSI", "WIC", "Section 8", "Medicaid", "ACA", "child tax credit", "earned income", "supplemental security", "housing voucher", "microsimulation", "population", "reform", "policy impact", "budgetary", "decile".
policyengine-uk
ALWAYS LOAD THIS SKILL FIRST before writing any PolicyEngine-UK code. Contains the correct API patterns for household calculations and population simulations using the new policyengine package (not policyengine_uk directly). Triggers: "what would", "how much would a", "benefit be", "eligible for", "qualify for", "single parent", "married couple", "family of", "household of", "if they earn", "with income of", "earning £", "making £", "calculate benefits", "calculate taxes", "benefit for a", "tax for a", "what would I get", "what would they get", "what is the rate", "what is the threshold", "personal allowance", "maximum benefit", "income limit", "benefit amount", "how much is", "Universal Credit", "child benefit", "pension credit", "housing benefit", "council tax", "income tax", "national insurance", "JSA", "ESA", "PIP", "disability living allowance", "working tax credit", "child tax credit", "Scotland", "Wales", "UK", "microsimulation", "population", "reform", "policy impact", "budgetary", "decile".
policyengine-canada
ALWAYS LOAD THIS SKILL FIRST before writing any PolicyEngine-Canada code. Contains Canadian federal and provincial tax/benefit rules for household calculations. IMPORTANT: PolicyEngine-Canada does NOT have representative population microdata. Do NOT attempt microsimulation or population-level estimates for Canada. Only provide household-level analysis (single-family impacts, eligibility, benefit amounts). Triggers: "what would", "how much would a", "benefit be", "eligible for", "qualify for", "single parent", "married couple", "family of", "household of", "if they earn", "earning $", "making $", "calculate benefits", "calculate taxes", "benefit for a", "what would I get", "what is the maximum", "what is the rate", "income limit", "benefit amount", "maximum benefit", "compare provinces", "CCB", "Canada Child Benefit", "GST credit", "HST credit", "GST/HST", "OAS", "Old Age Security", "GIS", "Guaranteed Income Supplement", "CWB", "Canada Workers Benefit", "EI", "Employment Insurance", "CPP", "Canada Pension Plan", "RRSP", "TFSA", "Ontario Child Benefit", "OCB", "Ontario Trillium Benefit", "OTB", "BC Climate Action", "Alberta Child Benefit", "Quebec", "CRA", "Canada Revenue Agency", "Canadian", "Canada", "Ontario", "British Columbia", "Alberta", "Saskatchewan", "Manitoba", "Nova Scotia", "New Brunswick", "PEI", "Newfoundland", "Yukon", "NWT", "Nunavut", "provincial tax", "federal tax Canada".
policyengine-ui-kit-consumer
This skill should be used when setting up a new project that uses @policyengine/ui-kit, debugging CSS or styling issues in a consumer app, or when Tailwind utility classes are not being generated. Also use when creating globals.css, configuring PostCSS, or troubleshooting "no styles", "no spacing", or "no layout" problems. Triggers: "ui-kit import", "globals.css setup", "Tailwind not working", "styles not applying", "utility classes missing", "setup ui-kit", "PostCSS config", "no styling", "CSS broken", "import ui-kit", "theme.css", "no layout", "no spacing", "@tailwindcss/postcss"
policyengine-tailwind-shadcn
Tailwind CSS v4 + shadcn/ui integration patterns for PolicyEngine frontend projects. Covers @theme namespaces, CSS variable conventions, SVG var() usage, and common mistakes. Triggers: "Tailwind v4", "@theme", "shadcn", "CSS variables", "design tokens CSS", "theme.css", "@theme inline"
Didn't find tool you were looking for?