Markdown & source details
Supplemental guide. Source snapshot: 8ed7f99e2c44d3093123dd0796d17416d4d541ad. Reading source: 8ed7f99e2c44d3093123dd0796d17416d4d541ad. Check the guide's compatibility requirements against your installed packages.
On this page
Testing helpers#
Runner-independent helpers for mounting real Marionette Views and Applications and awaiting cleanup of the resources your test owns. The core works with any DOM-capable test runner and your choice of assertions. Optional Vitest, Cypress and Playwright integrations adapt that same lifecycle; DOM Testing Library adds queries to an existing mount. Production Marionette does not depend on this development package.
Install#
Use Marionette 5 and Node.js ^22.22.2 || >=24.15.0:
npm install --save-exact marionette@5.0.1
npm install --save-dev --save-exact @mnjs/test@0.2.0
Install only the optional tools your tests use. Component tests can stay in Cypress; the helper does not require a second runner.
The helper is separately versioned: these examples use Marionette 5.0.1 with
@mnjs/test 0.2.0. A coordinated runtime patch does not require a helper release.
Use the runner-independent core#
Import mountRegion, mountView, mountApplication and createTestScope from @mnjs/test.
They register no runner hooks or commands. Mounting needs a DOM configured
before importing Marionette; a browser already supplies one. This Node example
assumes that preload has run:
import assert from 'node:assert/strict';
import { View } from 'marionette';
import { mountView } from '@mnjs/test';
const mounted = mountView(new View({ template: () => '<h1>Hello</h1>' }));
try {
assert.equal(mounted.el.textContent, 'Hello');
} finally {
await mounted.cleanup();
}
Use your runner's asynchronous teardown or try/finally to await cleanup. The
Mocha example includes the DOM preload and an
Application readiness check. Optional runner fixtures can manage this ownership
for you, as in the Vitest example below.
Write a test with Vitest#
For a JSDOM test with accessible DOM queries, add:
npm install --save-dev --save-exact vitest@5.0.3 jsdom@30.1.1 @testing-library/dom@10.4.1
Set the environment in vitest.config.js (or vitest.config.mjs if your project
uses CommonJS):
import { defineConfig } from 'vitest/config';
export default defineConfig({ test: { environment: 'jsdom', include: ['test/**/*.spec.js'] } });
Create test/greeting.spec.js:
import { expect } from 'vitest';
import { test } from '@mnjs/test/vitest';
import { withQueries } from '@mnjs/test/testing-library';
import { View } from 'marionette';
const Greeting = View.extend({ template: () => '<h1>Hello</h1>' });
test('shows the greeting', ({ mounts }) => {
const mounted = withQueries(mounts.mountView(new Greeting()));
expect(mounted.getByRole('heading', { name: 'Hello' }).textContent).toBe('Hello');
});
Run npx vitest run. The fixture awaits teardown after every test, including a
failing assertion; the test needs no mount registry or cleanup hook.
Choose your integration#
| Import | Use it for |
|---|---|
@mnjs/test |
mountRegion, mountView, mountApplication and createTestScope with any DOM-capable runner. Register and await cleanup yourself. |
@mnjs/test/scope |
createTestScope and owned Radio replies without importing Marionette or a runner. |
@mnjs/test/vitest |
A per-test mounts fixture with awaited teardown, including concurrent tests. Requires Vitest 4.1 or later. |
@mnjs/test/cypress |
Explicit command registration, queued mounts and awaited replacement/teardown. Requires Cypress 14 or later and mount-utils 4 or later. |
@mnjs/test/playwright |
A browser-side story gallery used with Playwright's mount fixture and Locators. Return browser cleanup from the test's afterEach. |
@mnjs/test/testing-library |
Standard queries bound to an existing mount's stable container. Requires DOM Testing Library 8.7 or later; adds no separate lifecycle. |
The neutral entry and gallery do not require the other runners. Mounting requires a DOM; importing the neutral entry and creating a cleanup scope do not.
Ownership and readiness#
A mount owns its View or Application and the fixture DOM it creates. Supplied containers, models, providers and channels remain borrowed. Register your own listeners and subscriptions with the test scope, or release them yourself.
Application mounts start by default. In this mode, await or handle mounted.ready:
it resolves true for initial startup, false for cancellation, and rejects on
failure. It does not wait for later saves or every asynchronous task.
{ start: false } skips startup and makes mounted.ready resolve false for
that skipped start, not cancellation. If the test starts the Application later,
await the Promise returned by app.start(); mounted.ready does not observe
that manual startup.
Always await cleanup. Runner fixtures do this for their owned mounts; neutral
handles need your runner's teardown hook or a try/finally. Cleanup errors reach
the runner and remain rejected on repeated calls. There is no universal leak
checker or automatic repair of partially failed framework operations.
Own resources at the right lifetime#
Each mount exposes mounted.scope. Its callbacks run in reverse order and finish
before the mounted shell, Region and helper DOM are destroyed. Calling
mounted.scope.cleanup() performs the same complete teardown as
mounted.cleanup(). A View factory receives this same scope; an Application
factory receives (region, scope).
mountRegion(options) returns an owned Region handle with this scope and cleanup
contract. Show or replace Views through mounted.region.show(view); cleanup owns
the current View as well as the Region. Supplied hosts and their siblings remain
borrowed.
In Cypress, testScope() from @mnjs/test/cypress synchronously returns fixtures that
survive remounts and unmount(). They are released after the final mount during
test teardown. Use mounted.scope for mount-local services and
mounted.testScope for those longer-lived fixtures. Pending Application startup
is cancelled first and can release prepared UI early; see the
cleanup contract.
Register setupCypress() before support modules that register root hooks. Use an
early side-effect setup module when those hooks are registered by imports; see
the support-file ordering.
In 0.1, the scope passed to a Cypress mount factory outlived the mounted shell.
In 0.2 it is mount-local and cleans up before that shell. Put fixtures that must
survive remounts in testScope() and keep shell-owned services in the factory scope.
For cleanup without mounting, use the runner-free @mnjs/test/scope entry.
The reply example also imports Radio. Install it directly with
npm install --save-dev --save-exact @mnjs/radio@5.0.1 if it is not already a
dependency of your project:
import { createTestScope } from '@mnjs/test/scope';
import { Radio } from '@mnjs/radio';
const scope = createTestScope();
scope.reply(Radio, 'account', 'current', () => ({ name: 'Ada' }));
// Or pass an already selected channel: scope.reply(channel, 'current', handler).
await scope.cleanup();
Reply cleanup removes only this registration if it is still current. It does not reset a channel or restore a previously overwritten handler.
Synchronous mount failures after resource acquisition throw MountError with the
original error as cause. Await its cleanup Promise before continuing; a cleanup
failure rejects with both causes. Cypress awaits this automatically. See the
full failure boundaries.
Version-matched reference#
This package includes its own consumer guide. Open
node_modules/@mnjs/test/guide.md from your project for complete runner setup,
all mount options, bound queries, readiness, ownership, teardown errors and tested
versions. Its examples use public package imports. This reference ships with the
helper; it does not require a newer documentation copy in the core marionette package.
The general Marionette testing guide explains testing rendered content, interaction, readiness and disposal.