Hello, community!
I have implemented a working experimental Meteor + Rstest integration to explore how Meteor testing could evolve after Rspack.
This implementation is fully working across the scenarios shown below, but it remains unpublished. This is not a package, beta, or public testing announcement. I am sharing the implementation and results now to continue the conversation and gather early feedback on the direction and architectural boundaries.
I am intentionally not opening another checkout-based testing flow while current published betas and existing checkout integrations need focused community attention. When there is enough time and focus to support broader experimentation properly, I will publish a step-by-step Meteor checkout walkthrough, similar to CapacitorJS.
With Rspack and the reusable modern-tool foundations already in place, I began using Rstest through a Meteor checkout in my own applications. What began as a practical tool for my work now runs end to end and offers a concrete direction for modernizing Meteor testing more broadly.
Why Rstest
Meteor’s testing gap is not a lack of assertion libraries. It is the lack of one modern workflow that can move from pure TypeScript and React components to Atmosphere packages, MongoDB, DDP, real-browser behavior, and full-application E2E without losing Meteor semantics along the way.
Rstest provides the testing experience I want for that workflow: a Jest-compatible API, projects, snapshots, coverage, watch mode, Browser Mode, Playwright integration, filtering, reporters, sharding, and worker parallelism. These are not abstract capabilities for me. I already rely on this mix of unit, component, snapshot, browser, E2E, coverage, and parallel testing in my own applications.
Its fit with Meteor goes beyond the feature list. Rstest is built around Rspack, so testing can follow the same modern compiler direction Meteor is already adopting instead of creating another parallel toolchain. Rstest alone, however, cannot understand meteor/*, Atmosphere packages, MongoDB, DDP, startup hooks, or Meteor’s application lifecycle.
That is the reason for this integration: bring Rstest’s modern testing workflows into Meteor without replacing real Meteor behavior with a framework-level mock layer. Portable tests should keep Rstest’s native speed and features. Meteor-dependent tests should keep the packages, compiler, services, and runtime semantics they use in production.
Rstest 0.11 is a transitional release on the road to 1.0, with public APIs being settled ahead of the stable release. That makes this a useful time to validate Meteor’s integration boundaries and evolve this work alongside Rstest as it matures.
The goal is one familiar Rstest API across the testing spectrum, with each test running in the environment that gives its results meaning.
What is already implemented
This is not a paper design. Meteor core changes, the generic test-runner provider lifecycle, the Atmosphere rstest package, @meteorjs/rstest, the Meteor runtime bridge, import-aware routing, package testing, Browser Mode, E2E, snapshots, unified coverage, watch mode, native concurrency, and isolated Meteor workers are implemented. The recordings below show these flows running end to end.
Two native execution paths
The implementation uses two execution paths, selected automatically when imports make ownership clear:
- Pure Node, jsdom, Browser Mode, snapshots, coverage, and external E2E tests run through upstream
@rstest/coreand its Rspack compiler. - Tests requiring
meteor/*, Atmosphere packages, MongoDB, DDP, WebApp, startup hooks, orPackage.onTestuse the same@rstest/coreAPI but execute inside a real Meteor host.
Developers use the same Rstest API in both paths. The integration does not mock Meteor. Tests requiring Meteor semantics run inside a real Meteor runtime built through existing Isobuild and Rspack plumbing. Meteor continues to provide package resolution, architecture slices, runtime services, MongoDB, DDP, client execution, and lifecycle management.
Both paths share Meteor-aware Rspack configuration layers. They do not pretend to share one live compiler.
This keeps both sides native. Portable tests use Rstest’s real engine and features. Meteor-dependent tests use the same upstream Rstest API while Meteor’s real compiler, packages, and runtime continue to handle Meteor behavior. Projects can still create intentional mocks for isolated unit tests, but this integration does not require them.
Working integration experience
The implemented flow keeps Meteor’s familiar commands. In the working branch, applications opt in with the test-only capability package:
meteor add rstest
Normal test commands remain familiar:
meteor test --once
meteor test
meteor test-packages --once my-package
Tests can remain beside their source files and do not need a routing directory. With the Rstest provider active, Meteor uses one Rspack dependency graph to classify direct and transitive imports. @rstest/core, @rstest/browser, @rstest/playwright, and reachable meteor/* imports route Rstest-owned files to native Node, Browser Mode, external E2E, or a real Meteor host.
Filename markers such as .native.rstest.test.ts, .meteor.rstest.test.ts, .browser.rstest.test.tsx, and .e2e.rstest.test.ts provide explicit opt-ins when globals or wrappers hide that intent. Existing tests/rstest/** roots remain compatible ownership hints, not a required layout.
Rstest CLI options pass unchanged after Meteor’s -- separator:
meteor test --once -- --reporters=verbose
This selects detailed case output without enabling broader Meteor diagnostics.
An explicit driver keeps its established route:
meteor test --once --driver-package meteortesting:mocha
meteor test-packages --driver-package test-in-browser my-package
Rstest does not imitate Mocha callback done, Mocha’s this context, Tinytest registration, or custom driver behavior. Import inference claims only Rstest-owned files. Existing tests remain on their real drivers and migrate incrementally only when projects opt in.
Working Rstest configuration
The implemented @meteorjs/rstest package provides a thin defineConfig wrapper. It returns normal Rstest configuration while providing immutable Meteor command context:
import { defineConfig } from '@meteorjs/rstest';
export default defineConfig(context => ({
retry: process.env.CI ? 2 : 0,
testTimeout: context.fullApp ? 30_000 : 10_000,
maxConcurrency: 5,
}));
Native Rstest configuration remains available: projects, reporters, Browser Mode, snapshots, coverage, tools.rspack, setup files, timeouts, and other upstream options. The same coverage configuration controls native tests and Meteor-hosted tests, including reporting, source filters, thresholds, failure reporting, and cleanup.
Using one API does not make both execution paths identical. Native tests keep full upstream Rstest behavior. Meteor runtime tests support the Rstest features that fit inside a real Meteor application while preserving Meteor’s runtime behavior. The implementation PR will document the remaining execution differences in detail.
Meteor generates safe defaults, keeping rstest.config.ts optional.
Browser Mode, Playwright, jsdom, and coverage dependencies remain project-owned opt-ins.
Working showcase coverage: Memory Match
The working implementation includes a small React and TypeScript Memory Match game. Recordings below show each supported testing boundary without presenting this work as ready for public setup.
Native unit tests
Rstest runs pure TypeScript game rules in Node through Meteor’s Rspack configuration. This lane includes parameterized tests, spies, snapshots, in-source tests, and native concurrent cases.

Watch mode
Meteor keeps its normal watch-oriented command experience while Rstest owns affected native test reruns. Watch mode starts by omitting --once.

Meteor runtime integration
Server and client tests execute inside a real Meteor application. They use methods, publications, MongoDB, DDP, Minimongo, Tracker, React reactivity, and Atmosphere package exports without mocks.

Atmosphere package tests
A local package opts in through Package.onTest, imports its tests from @rstest/core, and executes through Meteor’s real local-test:* Isobuild harness.

Full-application E2E
Meteor owns application, MongoDB, proxy, readiness, and cleanup. Rstest Playwright drives the running game, completes a match, verifies persistence, and observes reactive state from a second browser context.

React component tests
React components run through jsdom and Testing Library without starting a full Meteor application or MongoDB.

Real browser tests
Rstest Browser Mode launches Chromium and tests rendering, input, semantic locators, computed CSS, and browser-only behavior.

Coverage
Coverage now follows the same unified model as test execution. One Rstest configuration produces one Istanbul report across native tests, the real Meteor server and client, isolated Meteor workers, local Atmosphere packages and package tests, and full-application Playwright flows.
Meteor does not replace Rstest’s coverage API or reporting model. The integration merges coverage from each real execution environment before Rstest’s Istanbul provider generates one report. This keeps reports, source filters, thresholds, failure reporting, and output directories consistent across the complete run.
Native-only runs retain upstream Rstest coverage behavior, including Istanbul or V8. Runs that include a Meteor host currently use Istanbul because coverage must cross Meteor’s compiler and runtime boundaries before it can be merged accurately.
Some boundaries remain explicit. Coverage does not yet aggregate across watch generations or create synthetic zero-hit entries for included files that never execute. Sources compiled by custom Meteor compilers are skipped with a warning, while standard JavaScript and TypeScript package sources are covered. Playwright coverage belongs to the selected project-owned process; separate external browser processes remain outside that report.
The showcase verifies the merged report with native, Meteor server and client, package, worker, and browser-executed sources. It produces HTML and JSON reports from the same command.

Native Rstest parallelism
Rstest distributes pure test files across native worker threads. Concurrent cases inside each file use the configured maxConcurrency.

Isolated Meteor runtime workers
Meteor partitions server runtime files across separate application hosts. Each worker receives its own local build directory, ports, Meteor process, and Mongo database, then reports into one aggregate.
This differs from native Rstest workers. Runtime workers provide process and database isolation for tests that require real Meteor state.
The recording passes verbose Rstest output through Meteor’s – separator (-- --reporters=verbose).

Driver packages and test-runner providers
The showcase covers both native Rstest and real Meteor-runtime lanes because a test-runner provider participates earlier than existing driver packages. A driver package runs after Meteor has already created a test application. A provider participates before that decision.
After researching Meteor’s current testing architecture, Rspack integration, and previous community proposals, I believe this is the most natural boundary. Meteor core owns generic orchestration and the real runtime lifecycle. Rstest owns engine-specific execution and features. Existing drivers keep their established behavior.
Meteor did not have a test-runner provider concept before this work. This Rstest integration introduces and implements it as a generic Meteor core extension point, with Rstest as its first working provider. It lets an external test tool participate before Meteor constructs a test application, run native work directly, request Meteor hosts only where needed, and aggregate every result through one command.
Meteor keeps responsibility for application builds, Atmosphere packages, MongoDB, ports, runtime services, and cleanup. Rstest keeps responsibility for its test API and native execution features. The provider joins both sides when a test needs a real Meteor runtime.
This distinction lets Rstest run pure tests without starting Meteor while still requesting a complete Meteor runtime when a test needs one.
At the package level, the implementation follows Meteor’s existing Rspack structure:
- Atmosphere
rspackkeeps application compiler lifecycle ownership. @meteorjs/rspackprovides shared Meteor-aware Rspack configuration.- Test-only Atmosphere
rstestowns Rstest activation, tool-host provider, and Meteor runtime executor. @meteorjs/rstestowns dynamic configuration, generated projects, native Rstest coordination, and CLI mapping.- Meteor core provides a generic test-runner provider lifecycle without hardcoding Rstest.
Rstest follows the modern tooling direction established by Rspack and extended through Capacitor: package-based activation, shared tools-core services, and tool-specific logic outside Meteor core. With meteor.autoInstallDeps enabled, it installs only its minimum npm dependencies, while browser and coverage extras remain project-owned and opt-in.
This provider layer is broader than Rstest. It creates a reusable foundation for Vitest, native node:test, or another engine without making Meteor core understand each tool. Every engine would still need its own provider, configuration bridge, dependencies, and Meteor runtime executor.
Building on community testing work
I built this alongside my Meteor core responsibilities while spending the rest of my development time evolving real Meteor applications. That balance keeps framework decisions grounded in application work. It also keeps recurring needs visible: snapshots and other modern testing features, real meteor/* and Atmosphere package resolution, fewer framework mocks, and faster, less fragmented development and CI feedback. The same application perspective helped spark my work on Rspack and Capacitor, and Rstest continues that direction.
I have been encouraging Meteor to use Rstest as its next modern testing path since Rspack made that direction practical. In February 2026, I argued that Meteor testing should evolve beyond Mocha and suggested Rstest for faster execution, snapshots, and flexible testing strategies. In March, I raised Rstest directly in PR #14216, later highlighting its alignment with SWC and Rspack and identifying package, application, and core integration as its central challenge. Those boundaries now work end to end.
This direction also builds on years of community discussion and experimentation:
- @jankapunkt opened the broader Tinytest modernization discussion, including headless package testing and coverage.
- @zodern built
mtestand highlighted false-green risks when client tests fail to participate. - @StorytellerCZ proposed using Node’s native test runner as a modern foundation.
- @italojs built a Node-oriented Meteor driver with a custom sequential server runner and browser coverage through existing headless-driver plumbing.
- @dupontbertrand explored native
node:testthrough PR #14216, deterministic server execution through PR #14477, and a parallel-first runner RFC. - @harryadel explored structured headless Tinytest execution for terminal and agent workflows in PR #14224.
- @ceigey identified Rstest as a possible foundation for a future Tinytest evolution, with a compatibility layer above a common runner.
- @radekmie emphasized that DOM emulation cannot replace real-browser coverage for isomorphic code.
I used these contributions as design constraints, not competing directions. This integration does not replace other Meteor testing work. Existing runners can continue evolving through driver packages and their current integration points, while improvements to Tinytest, Mocha, Node-based runners, browser harnesses, and shared Meteor testing infrastructure remain valuable on their own. Rstest can evolve further through the provider boundary alongside them, without requiring Meteor projects to converge on one runner.
Replacing an assertion API is not enough; Meteor also needs process lifecycle, browser participation, package compilation, database isolation, structured results, and reliable cleanup. Compared with approaches centered on a custom Meteor runner or plain Node, this implementation keeps both environments native: Rstest runs portable tests directly, while Meteor-dependent tests use the same @rstest/core API inside the real Meteor runtime without a framework-level mock layer between them.
The Meteor roadmap still lists Test toolkit Improvements as a major priority: better integration with popular frameworks, faster test runners, and a stronger developer experience. This implementation provides a concrete answer. For now, I welcome feedback on the direction, architectural boundaries, and missing edges.
