Skip to main content
Meridian uses Jest on the backend and frontend. Treat tests as part of the feature: new behavior should come with tests (or a deliberate, rare exception called out in the PR). CI runs on every pull request and on pushes to main.

Run from Meridian/

These scripts match what you should run locally before opening or updating a PR:
Commands assume the Meridian app root (Meridian/ in the monorepo) and installed dependencies in Meridian/, Meridian/backend/, and Meridian/frontend/.

Expectations when writing new code

General

  • Deterministic: no real network, no wall-clock races, no dependence on “today’s” date unless frozen or injected. Use in-memory Mongo for backend route tests, not a shared dev DB.
  • Readable failures: assertion messages or test names should make regressions obvious in CI logs.
  • Colocate with the change: tests live next to the layer they protect (see layout sections below). Avoid giant catch-all files unless that pattern already exists for the area you are editing.

Backend

Route-outcome tests run real handlers against mongodb-memory-server. They are the preferred way to lock API behavior because they exercise persistence and response shape the way production does, without flaking on a shared database. Multi-tenant: handlers expect req.db, req.school, and sometimes req.globalDb. Route-outcome tests must set that context the same way production middleware does. If a route uses global identity (authGlobalService, etc.), use createMongoMemoryConnection({ withGlobalDb: true }) and assign req.globalDb per docs/TESTING_FRAMEWORK.md and backend/tests/helpers/mongoMemory.js.

Frontend

Use React Testing Library the way the existing suite does: assert on what users see, not implementation details unless unavoidable. Mock window, cookies, or browser-only APIs deterministically.

Before you open a PR

  1. Run npm run test:ci from Meridian/ (or at minimum the package you touched: npm run test:backend / npm run test:frontend).
  2. If you only changed backend HTTP behavior, npm run test:backend:routes is a quick gate before the full coverage run.
  3. If CI fails on coverage or unit-tests, reproduce locally with the same npm scripts as in the workflow (below).

Backend (Meridian/backend/)

Stack: Jest, Supertest, mongodb-memory-server for route outcomes.

Layout

  • backend/tests/unit/ — isolated logic and utilities.
  • backend/tests/integration/ — HTTP behavior against the app.
  • backend/tests/route-outcomes/ — end-to-end request/response + Mongoose on in-memory DB.
Config: backend/jest.config.js. Helpers: backend/tests/helpers/mongoMemory.js.

Frontend (Meridian/frontend/)

Stack: Jest + React Testing Library (runner via scripts/test.js).
Tests: frontend/src/**/__tests__/**/*.test.js and targeted utils tests for request helpers.

Jest basics (if this stack is new)

Meridian uses the same Jest runner everywhere:
  • describe('…', () => { … }) — groups related specs (often one file per module or route file).
  • test('…', () => { … }) or it('…', …) — a single example; use async when the body awaits promises.
  • expect(value).toBe(…), .toEqual(…), .toContain(…), expect(fn).toHaveBeenCalled() — assertions; Jest fails the test when any assertion fails.
  • beforeEach / afterEach / beforeAll / afterAll — setup and teardown (open DB, reset mocks, close connections).
  • jest.mock('modulePath', factory) — replace a module with a fake for the current test file (common in route and integration tests).
Backend HTTP tests use Supertest: request(app).get('/path') returns a response object with statusCode, body, headers, etc.

Minimal patterns (Meridian-shaped)

The snippets below are small teaching sketches—valid Jest/Supertest style, heavily commented, focused on syntax and Meridian conventions. They are not copy-pasted production tests. For full imports, schemas, and beforeEach seed data, open Meridian/backend/tests/** and Meridian/frontend/src/**/__tests__.

1) Backend unit — no req, no Mongo

2) Backend HTTP — Supertest + mocked infra

3) Backend route outcomes — req.db, getModelService, in-memory Mongo

Meridian handlers use getModels(req, 'User', 'Event', …) (through getModelService) so every query runs on the tenant Mongoose connection attached as req.db. Route-outcome tests reproduce that with mongodb-memory-server (createMongoMemoryConnection in backend/tests/helpers/mongoMemory.js) and a jest.mock('../../services/getModelService', …) factory that binds schemas with getOrCreateModel(req.db, …)—never mongoose.model() on the global connection for tenant data.
In production tests you then: jest.mock('../../services/getModelService', …) returning getOrCreateModel(req.db, …) per schema, mount the real router, and assert both HTTP and documents in the in-memory DB. Meridian/backend/tests/route-outcomes/ has full examples (including withGlobalDb when routes need req.globalDb). Meridian-specific checklist
  • Attach req.db (and req.globalDb when the route uses global identity) before handlers run.
  • Mock getModelService so getModels(req, …) matches production binding.
  • Reset the in-memory database between tests (mongo.reset()).
  • Assert HTTP and, when it matters, documents in Mongo via the same models the route used.

4) Frontend — postRequest and axios mocks (no browser network)

5) Frontend — components (React Testing Library)

For data-loading components, mock useFetch or the module that wraps postRequest so tests never hit localhost:5001 accidentally.

CI integration (Meridian/.github/workflows/ci.yml)

Workflow file: .github/workflows/ci.yml. Triggers: pull_request and push to main. Meridian CI is not a single “test” step: it uses three jobs so build, fast test signal, and coverage artifacts stay separated.

1. build

  • Validates private-deps.lock (events.ref must be a 40-character hex SHA).
  • Sets up SSH, runs bin/fetch_private_deps (Events-Backend and other private deps for a faithful tree).
  • npm install in backend/ and frontend/.
  • Runs npm --prefix frontend run build (with CI=false for that step as defined in the workflow).
Failing here means the app does not compile or lockfile/deps are invalid—fix before expecting tests to pass.

2. unit-tests

Same lockfile + SSH + fetch_private_deps + installs, then:
  • npm --prefix backend run test:unit
  • npm --prefix backend run test:integration
  • npm --prefix backend run test:routes
  • npm --prefix frontend run test:ci
This job runs backend suites separately (no coverage gate in these steps) and the frontend CI test script. Failures here are usually a specific Jest file or suite.

3. coverage

Same bootstrap, then:
  • Backend: npm --prefix backend run test:coverage with JSON test output under backend/coverage/.
  • Frontend: npm --prefix frontend run test:coverage with lcov, clover, json-summary, and JSON test results under frontend/coverage/.
Afterwards:
  • A step summary is appended to the GitHub Actions run (tables built from coverage/test-results.json and coverage-summary.json where present).
  • Artifacts are uploaded: backend and frontend lcov, summaries, HTML lcov reports, and related files for download from the run.
So: npm run test:ci locally aligns with running the full coverage-style backend + frontend commands you rely on for merge confidence; CI additionally splits unit / integration / routes in the unit-tests job and runs a production build in build.

Going deeper

In the Meridian repo, docs/TESTING_FRAMEWORK.md is the canonical in-tree reference for layout, multi-tenant route tests, and “adding new tests” checklists.

Getting started

Stack, meridian setup, and commands to run before npm run test:*.

Development

Day-to-day workflow, debugging, and troubleshooting.

Backend best practices

req.db, auth middleware, and patterns you assert on in route tests.

Multi-tenant test scenarios

Tenant isolation and membership cases worth mirroring in new tests.

Authentication overview

How login and tokens work when testing auth and refresh flows.

Web client best practices

useFetch / postRequest patterns to reflect in frontend tests.

Meridian CLI

Branch helpers and local workflow around the same repo you test.

Deployment

CI jobs, coverage artifacts, and how merge gates align with local test:ci.