Testing
Refract uses two test runners: Jest for the backend (and tool packages) and Vitest for the frontend. They share a similar API, but they’re configured and invoked separately. Here’s everything you need to get comfortable writing and running tests.Backend: Jest
The backend uses Jest withts-jest — which means tests are written in TypeScript and compiled on the fly, no separate build step needed.
Config (apps/backend/jest.config.json)
src/tools/rds/sequelize/setup.ts which runs your migrations against the test database. After all tests finish, tearDown.ts cleans up. This means your tests always have a freshly migrated schema available, without any test managing that setup itself.
clearMocks: true — Jest automatically resets all mock state between tests. You don’t need to call jest.clearAllMocks() in afterEach.
moduleNameMapper — the @/ path alias resolves to src/. So import { foo } from '@/utilities/foo' works in tests the same way it does in production code.
Running backend tests
Coverage reports
Coverage output lands inapps/reports/ after each run:
backend-coverage.json— machine-readable coverage data (used by the CI coverage diff action)backend.xml— JUnit XML (used by some CI reporting tools)
integration.yml workflow posts a coverage diff comment on the PR showing you exactly which lines your changes covered or uncovered.
Frontend: Vitest
The frontend uses Vitest — it’s configured insideapps/frontend/vite.config.ts under the test key, which means it shares Vite’s module resolution and transform pipeline:
environment: 'jsdom'gives your tests a browser-like DOM.globals: truemeans you don’t need to importdescribe,it,expect— they’re available globally, just like Jest.setupFilesrunssrc/test/setup.tsbefore each test file — this is where global test setup lives (custom matchers, mock resets, etc.).
Running frontend tests
Where tests live
Tests always live in a__tests__/ directory right next to the code they test. The pattern is the same for backend and frontend — co-locate the test folder with the source file.
Backend:
Mocking patterns
The backend uses Jest and the frontend uses Vitest. Their mock APIs are nearly identical, but the import is different —jest.* for backend, vi.* for frontend.
Backend: mocking dynamic imports (loader tests)
The queue, logger, and metrics loaders use dynamicimport() to load the right implementation at runtime. In tests, you mock these at the module level with jest.mock:
Backend: mocking Stripe operations
Thetools.paymentProcessor object is a plain object of functions, which makes it trivially mockable with jest.spyOn:
test environment, the payment processor is already configured as stripeMock — a no-op stub that returns { success: false } for everything. Use jest.spyOn when a test needs a specific successful response. Never mock the real Stripe SDK.
Backend: the tools object
Most backend tests that touch database or external services receive a tools object. The test environment wires this up via apps/backend/src/configuration/test.ts. You don’t construct it manually — it’s the same dependency-injection pattern as production, just with mock or test implementations substituted in.
Frontend: mocking with Vitest
Frontend tests usevi.fn() and vi.mock() — the Vitest equivalents of Jest’s mock utilities:
@testing-library/react is already set up — use render, screen, and fireEvent for component tests. The jsdom environment means document and DOM queries work exactly as you’d expect in a browser.
Tool package tests
Tool packages (apps/tools/*/*) also have Jest configs and their own __tests__/ directories. Run them with:
integration.yml CI pipeline in the tests job.
What’s next?
- Continuous Integration — how tests run in CI, coverage diffing, and the
codegen-verificationjob. - TypeScript — how ts-jest picks up the TypeScript config.
- Linter — keeping code clean before it ever gets to tests.