Developer docs

Unit Tests in CI

Overview

This page describes how unit tests run in CI for vets-website: how they're executed, how they're isolated, and two behavioral changes you should know about — per-app process isolation and the removal of the flaky-test allow list.

Unit tests use Mocha with a JSDOM-based browser environment (mocha-setup.js handles JSDOM, MSW, timers, component-library patching, and global setup). Which tests run for a given change is decided by test selection — see Test Selection in CI — not by the execution step described here.

Per-app process isolation

What it is: In CI, unit tests now run in a separate Mocha process per app directory. Each app's tests get their own process, so state cannot leak from one app's tests into another's.

Why it changed: CI previously ran all selected tests in a single Mocha process, while local runs executed each app's tests in an isolated process. That mismatch meant a test could pass locally but fail in CI (or the reverse) purely because of shared process state — globals, JSDOM, or shared mocks not cleaned up between suites. Per-app isolation makes CI match the proven full-suite model and the local model, so results are consistent across environments.

What you'll notice: Isolation can surface new failures in tests that quietly depended on state set up by another app's tests. These are real bugs being exposed, not regressions — the test was previously passing only because another suite happened to leave the right global state behind. Fixing them means making the test set up (and tear down) the state it needs, rather than relying on another suite.

Why it's worth it: Running a separate process per app adds some startup overhead versus one shared process. That cost is accepted because it's the only reliable way to avoid cross-app state pollution — the alternative is false failures from polluted state, which are far more expensive to chase.

Cross-app state pollution is known tech debt

The pollution these isolated processes work around (shared globals, JSDOM state, mocks not cleaned up between suites) is pervasive and documented as known tech debt. Fixing every root cause would mean auditing hundreds of test files across dozens of apps. The isolation model is designed to produce reliable results without requiring every test to be perfectly clean — but if isolation surfaces a failure in your app, that's a real cleanup opportunity in your tests.

The flaky-test allow list has been removed

What it was: A quarantine system that skipped known-flaky unit tests so they wouldn't fail CI. It read a list from an external data source and skipped those specs at runtime.

What changed: The allow list is gone entirely — no external data dependency, no runtime skipping. Previously quarantined tests now run and can fail if they're genuinely flaky.

What replaces it: In-PR stress testing. When you add or change a spec, CI runs it repeatedly on your PR to catch flakiness before it merges (see below). Combined with rules-based selection running fewer, more-relevant tests, this keeps flaky exposure in check without a standing quarantine list.

What you'll notice: If a test that was previously on the allow list is flaky, it can now fail your run. Treat it as a real signal — either the test or the code under test needs stabilizing.

In-PR stress testing (retained)

When your PR adds or changes a unit spec (*.unit.spec.js(x)), CI stress tests that spec — running it repeatedly to check it passes consistently before it's introduced to the suite. This runs on feature branches only. It is the stability gate that replaces the old allow-list quarantine.

The separate overnight unit stress-test workflow (unit-test-stress-test.yml) has been removed; stability validation now happens in-PR, where it's actionable.

How a unit test run works in CI

At a high level, the CI unit-test job:

  1. Checks out the code and installs dependencies.

  2. Downloads the selected test list produced by the selection step (unit_tests_to_run.json).

  3. Runs Mocha per app directory (isolated processes), against the selected patterns, logging the selection reason for visibility.

  4. Archives results and uploads coverage.

Execution is decoupled from selection: the runner doesn't decide what to test, it runs the list it's given. The full suite can be run on demand via the manual full-suite workflow.

What you need to do

Nothing changes in how you write or run unit tests. Two things to keep in mind:

  • If isolation surfaces a new failure in your app after the switchover, it's most likely a real cross-app-state dependency in the test — fix the test to set up its own state.

  • If a previously-quarantined flaky test starts failing, treat it as a real stability issue to address.

If you believe a failure is caused by the CI execution model itself (not your test), reach out to the Platform SRE team via the channel in the change announcement.