It’s still not working and quite frankly I’m starting to regret switching to rspack with such a fundamental feature not working properly. It’s seriously affecting my workflow, running tests that are touched by code editing is a fundamental principle of coding and with over 1000 tests it’s a serious problem. Claude Code (Opus 4.6) tried it’s best last night again, trying different options but in the end all failed. Here’s is its summary (which quite frankly I have no time to look into the details as a Solo Entrepreneur):
Meteor 3 + rspack + meteortesting:mocha — Dual Mocha Instance Problem
Environment
- Meteor 3.x with @meteorjs/rspack bundler
- meteortesting:mocha@3.3.0 / meteortesting:mocha-core@8.2.0 (Mocha 10.2.0)
- package.json has mainModule set:
“meteor”: {
“mainModule”: {
“client”: “client/main.js”,
“server”: “server/main.js”
}
}
How tests are initiated
MONGO_URL=“mongodb://…”
TEST_CLIENT=0
MOCHA_TIMEOUT=60000
meteor test --once
–driver-package meteortesting:mocha
–settings settings-development.json
–port 4001
The meteortesting:mocha package must first be added via meteor add meteortesting:mocha. The run_tests.sh script in the repo automates starting the MongoDB replica set and
running this command.
Build architecture (how rspack structures the test bundle)
When meteor test runs with rspack, the build produces these files in _build/test/:
server-entry.js → Entry point (imports app code + test files)
server-rspack.js → Bundled output from rspack (38MB, IIFE-wrapped)
server-meteor.js → Meteor runtime file that imports server-rspack.js
The load chain at runtime:
- Meteor boots — loads all Meteor packages (including meteortesting:mocha-core and meteortesting:mocha)
- Meteor loads server-meteor.js — which contains import ‘./server-rspack.js’
- server-rspack.js evaluates — all app code and test files execute inside the rspack IIFE
- Meteor.startup callbacks fire — including the test driver’s start() function
- start() calls mochaInstance.run() — runs the mocha test suite
The problem: two separate Mocha instances
Instance 1: Meteor package space (mocha-core)
In meteortesting:mocha-core/server.js:
import Mocha from ‘mocha’
import config from ‘./loadConfig’
const mochaInstance = new Mocha({ ui: ‘bdd’, ignoreLeaks: true, …config })
setupGlobals(mochaInstance) // sets global.describe, global.it, etc.
export { mochaInstance }
This mochaInstance lives in Meteor’s package module system. The test driver (meteortesting:mocha/server.js) imports it and calls mochaInstance.run() to execute tests.
Instance 2: rspack bundle space
When a test file inside the rspack bundle does:
import { mochaInstance } from ‘meteor/meteortesting:mocha-core’;
rspack resolves this as an external:
// In server-rspack.js:
“meteor/meteortesting:mocha-core”(module) {
module.exports = require(“meteor/meteortesting:mocha-core”);
}
This require(“meteor/meteortesting:mocha-core”) call at runtime goes through Node’s require system. However, the Meteor package system’s module resolution for packages loaded
from app code (inside rspack) returns a different module instance than the one loaded from package code (inside meteortesting:mocha).
Evidence
I verified this step by step:
Step 1: Inside server/tests.js (which runs inside the rspack bundle), I imported mochaInstance and called describe:
import { mochaInstance } from ‘meteor/meteortesting:mocha-core’;
console.log(‘suites BEFORE:’, mochaInstance.suite.suites.length); // 0
describe(‘Smoke test’, function () {
it(‘should pass’, function () {});
});
console.log(‘suites AFTER:’, mochaInstance.suite.suites.length); // 1
console.log(‘total tests:’, mochaInstance.suite.total()); // 1
Output:
suites BEFORE: 0
suites AFTER: 1
total tests: 1
----- RUNNING SERVER TESTS -----
0 passing (0ms)
The suite registers correctly on the mochaInstance visible inside the rspack bundle. But when the test driver calls mochaInstance.run(), it gets 0 tests — because it’s running
on a different mochaInstance.
Step 2: I checked from inside Meteor.startup (which fires during the test run):
Meteor.startup(function () {
console.log(‘suites at startup:’, mochaInstance.suite.suites.length); // 0
console.log(‘total tests:’, mochaInstance.suite.total()); // 0
});
Output:
----- RUNNING SERVER TESTS -----
suites at startup: 0
total tests: 0
0 passing (0ms)
By Meteor.startup time, the mochaInstance visible to the rspack bundle shows 0 suites. This proves the module is being re-evaluated (a fresh new Mocha() is created) when
accessed from different contexts.
Approaches tried
Approach 1: testModule in package.json
“testModule”: { “server”: “server/tests.js” }
Result: rspack’s @meteorjs/rspack plugin does not read testModule from package.json. The debug log I added to rspack.config.js showed:
isTestModule=false testEntry=undefined testServerEntry=undefined
The rspack plugin receives these values from the Meteor build tool’s Meteor object passed to defineConfig(). Meteor does NOT populate Meteor.testServerEntry when testModule is
set in package.json — this is a disconnect between Meteor’s package.json schema and what it passes to the rspack plugin.
However, Meteor does auto-generate _build/test/server-entry.js with import ‘…/…/server/tests.js’ when testModule.server is set. So the file IS loaded. But the tests inside it
still can’t register on the correct Mocha instance.
Approach 2: BannerPlugin to inject mocha globals
new BannerPlugin({
banner: [
‘var describe = global.describe;’,
‘var it = global.it;’,
‘var before = global.before;’,
// …
].join(‘\n’),
raw: true,
entryOnly: true,
})
Result: The banner correctly injected var describe = global.describe at the top of server-rspack.js, OUTSIDE the IIFE. I verified:
- typeof describe === ‘function’ ✓
- describe() doesn’t throw ✓
- The suite registers on mochaInstance inside the bundle ✓
But it’s the wrong instance — the test driver’s mochaInstance.run() operates on a different object.
Approach 3: Re-running setupGlobals from inside the bundle
import { mochaInstance, setupGlobals } from ‘meteor/meteortesting:mocha-core’;
setupGlobals(mochaInstance);
Result: Same problem. The mochaInstance imported inside the rspack bundle is a separate instance from the one the test driver uses. Re-running setupGlobals on it just sets
global.describe/global.it to point to this separate instance’s suite. Tests register on it, but the driver’s run() still finds 0 tests.
Approach 4: Eager test mode (no testModule, let rspack auto-discover)
Without testModule, rspack sets isTestEager=true and generates .meteor/local/test/server-eager-tests.mjs:
const ctx = import.meta.webpackContext(‘/’, {
recursive: true,
regExp: /.(?:test|spec)s?.[^.]+$/,
exclude: /…node_modules|.meteor|client…/,
mode: ‘eager’,
});
ctx.keys().forEach(ctx);
Result: This auto-discovers all *.test.js files and they DO get compiled into the 38MB bundle (I found describe calls at lines 128922, 722301, etc.). But the same dual-instance
problem means 0 tests run.
Root cause
The Meteor package module system creates separate module evaluation contexts for:
- Package-to-package imports: When meteortesting:mocha imports from meteortesting:mocha-core, it gets the canonical module instance
- App-to-package imports: When rspack-bundled app code does require(“meteor/meteortesting:mocha-core”), it gets a different evaluation of the same module
Since mocha-core/server.js creates const mochaInstance = new Mocha(options) at the top level, each evaluation creates a FRESH Mocha instance with its own root suite. Tests
register on the app-space instance, but run() executes on the package-space instance.
Suggested fix directions @nachocodoner
- In @meteorjs/rspack: Ensure that require(“meteor/…”) calls inside the rspack bundle go through the SAME module cache as the Meteor package system. This would make
require(“meteor/meteortesting:mocha-core”) return the identical mochaInstance object.
- In meteortesting:mocha-core: Instead of exporting mochaInstance directly, store it on global (e.g., global.__mochaInstance) and have both the test driver and the app code
reference it from there. This bypasses module system differences.
- In meteortesting:mocha: In the start() function, before calling mochaInstance.run(), merge suites from global.__rspackMochaInstance (if set) into the driver’s mochaInstance.
This would be a compatibility shim.