graphdb-workbench

Testing conventions

Test the public-facing behaviour of a unit, and mock at the lowest possible level. Coverage comes from driving the real stack through its public surface — not from a spec per class, method, or function.

This guide covers unit tests (Jest). Cypress specs follow e2e-testing.

Contents

The core rule

Test behaviour that a consumer depends on, through the seam that consumer uses.

A unit’s public surface is the set of methods and outputs some other code relies on: a domain service’s commands, a context service’s subscriptions and snapshots, a component’s rendered DOM and emitted events, a utility’s return value. That surface is what a test drives. Everything reached behind it — the mapper a service calls, the response model it parses, the REST service it awaits, its own private methods — is implementation and is covered indirectly, as a side effect of testing the public behaviour.

Two consequences:

What is “public” here

Public is defined by who consumes it, not by the public/private keyword or by providedIn: 'root' (almost everything is injectable — that doesn’t make it a test target).

Layer Public surface — test this Covered indirectly — don’t spec directly
Domain slice (packages/api/src/services/domain/*) the service’s commands and the context service’s on* / snapshot API its REST service, mappers, response models
Component / page rendered DOM, emitted outputs, input reactions its private methods
Domain model its behaviour as observed through the service or component that exposes it —

A private method that you feel you must reach directly is the signal that it — or the class around it — is badly factored. Extract it into its own unit with its own public reason to exist, or restructure so the public API exercises it. Adding a test hook (making it public, as any, @VisibleForTesting) treats the symptom.

A public method must earn its visibility from a real consumer, not from a test. If the only caller of a public method is its spec, it should be private (and tested through the method that is public), or it doesn’t belong in the class.

Exceptions — test these directly

Genuinely standalone, reusable units are themselves the public surface, so they get one focused spec each:

If you’re unsure whether something is an “exception”: ask who else calls it. Many independent callers, no natural higher surface → test it directly. Exactly one caller that is itself tested → fold the coverage into that caller’s test.

Unit tests and the browser

Split by whether the check needs a real browser.

Worked example — a domain slice

For the license slice in packages/api/src/services/domain/license, the consumer-facing surface is LicenseService (commands) and LicenseContextService (state). So:

Mechanics

Why

Testing through the public surface means the suite asserts behaviour users and callers depend on, so it stays green through refactors that move logic between a service, its mapper, and its private helpers — exactly the changes that a per-function spec would break for no behavioural reason. Fewer, higher tests also mean each covers a real contract, and a gap in them points at a missing capability rather than a missing line. And the discipline feeds back into design: if behaviour is hard to reach from the public API, that is the test suite telling you the module seam is in the wrong place.