Development packagesSupplemental guide
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.