> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hr-easy.nlead.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> What to test, how, and the gate that stops logic changes shipping without a test.

| Kind       | Tool                     | Location                        |
| ---------- | ------------------------ | ------------------------------- |
| Unit       | Vitest                   | `*.test.ts` next to the source  |
| Component  | Vitest + Testing Library | `*.test.tsx` next to the source |
| End-to-end | Playwright               | `e2e/`                          |

```bash theme={null}
npm test              # unit + component
npm run test:watch
npm run test:coverage
npm run test:e2e
npm run test:scripts  # bash tests for the deploy-pipeline helpers
```

Targets: **80% coverage on business logic, 60% overall.**

## What makes a good test

<AccordionGroup>
  <Accordion title="Test behaviour, not implementation">
    A test that breaks when you rename a private variable is testing the wrong thing.
  </Accordion>

  <Accordion title="Test the failure case">
    Every function that can fail needs at least one test per failure mode. The happy path is the
    case least likely to regress.
  </Accordion>

  <Accordion title="Name tests as sentences">
    `it("returns 403 when caller does not own the resource")` — the name is the specification.
  </Accordion>

  <Accordion title="Mock at the boundary">
    Mock the database, the HTTP client, the external service. Never mock an internal helper — that
    tests the mock.
  </Accordion>

  <Accordion title="Assert specifically">
    `expect(result).toBeDefined()` is nearly useless. `expect(result.status).toBe(403)` is a test.
  </Accordion>

  <Accordion title="No interdependency">
    Every `it()` must run in isolation. Never rely on state set up by a previous block.
  </Accordion>

  <Accordion title="No inline Date.now() or Math.random() mocks">
    Inject them as parameters, or use `vi.useFakeTimers()`.
  </Accordion>
</AccordionGroup>

## Accessibility assertions

Every new interactive component includes an axe assertion — see
[Frontend conventions](/development/frontend#accessibility).

## Security fixes

Three tests, every time: the attack payload, the `401` boundary, the `403` boundary.
See [Security conventions](/development/security#every-security-fix-ships-with-tests).

## The regression-test gate

If a PR's diff touches a **service** or an **API route**, the same diff must add or
modify at least one test file:

```bash theme={null}
npm run check:regression-tests
```

It does not demand a test per file — it demands that logic changes are not shipped with
zero new tests. It **fails open**: if the base ref or diff cannot be resolved, it skips
rather than blocking, so it can never become a flaky wall.

Two auditable bypasses:

* `[no-test]` in the HEAD commit subject or body — for a genuine refactor, config or
  docs-adjacent change.
* A path glob in `scripts/regression-test-allowlist.json`, with a reason.

<Note>
  The gate exists because a PR once claimed a test in its commit message that was never committed.
  CI proves the code compiles and existing tests pass; nothing else proved a logic change came with
  a test.
</Note>

## Structural twins

Where two functions must stay in sync by construction — parallel resolvers, an alias
map and its consumer, a `GET` and a `POST` that share a guard — pin them with a
**same-shape test**, so divergence fails CI instead of surviving until someone notices.

The alias lockstep test in `src/lib/contracts/variables.test.ts` is the reference
pattern, and the [OpenAPI artifact paths](/api-reference/introduction) use the same
idea.

## Tests that pin process

Some of the repository's own automation is specified in `src/lib/pipeline/` with tests
next door — the bot-review summary regex, PR readiness, the path classifier. Bash
implementations mirror those specs and are kept honest by `npm run test:scripts`.

If you change the automation, change the spec and its test together.
