fix: await Meteor.startup() queue before running tests - #177
Conversation
In Meteor 3.x, async startup callbacks and top-level await can cause mocha.run() to fire before all Meteor.startup() callbacks have completed. This leads to flaky tests when server initialization depends on async startup work (e.g. ensuring MongoDB indices). Await a promise resolved by a Meteor.startup() callback at the end of the queue, ensuring all prior callbacks (including async ones) have finished before tests begin. Fixes Meteor-Community-Packages#176 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a server test that registers an async Meteor.startup() callback with a short delay and asserts it completed before tests run. Without the fix in server.js, mocha.run() fires before the callback finishes, causing this test to fail. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
||
| // Before Meteor calls the `start` function, app tests will be parsed and loaded by Mocha | ||
| function start() { | ||
| async function start() { |
There was a problem hiding this comment.
The returned Promise from start() is silently discarded in packages/meteor/test_environment.js:59-61
if (typeof testDriverPackage.start === "function") {
testDriverPackage.start(); // ← no await, no .catch()
}Making start() async means it now returns a Promise. The Meteor framework does not await it. Any rejection inside start() becomes an unhandled Promise rejection (which in Node 18+ terminates the process with a cryptic error, not the actual stack trace).
I reccommend you to handle it in test_enviroment
There was a problem hiding this comment.
to avoid the async/await snallbal, try to handle it like
function start() {
new Promise(resolve => Meteor.startup(resolve))
.then(() => { /* move current start() body here */ })
.catch(err => {
console.error('meteor-mocha: failed to start', err);
process.exit(1);
});
}| // startup queue to still be draining when the test driver's start() is called. | ||
| // Adding a callback at the end of the queue ensures it runs after all prior | ||
| // callbacks have finished. See: https://github.com/Meteor-Community-Packages/meteor-mocha/issues/176 | ||
| await new Promise(resolve => Meteor.startup(resolve)); |
There was a problem hiding this comment.
if any previously-registered Meteor.startup() callback throws, the behavior depends on Meteor's internal error handling in the startup queue, but the await inside start() won't catch those errors. More importantly, if the await itself rejects for any reason, there's no error handling in start() to produce a meaningful message
|
Thanks for the review @italojs — you're right that Meteor doesn't await
That makes the In the meantime, I'll update this PR to use the |
|
@perbergland Once you update this PR, ping me so we can move it forward! Thanks! |
|
This is such an old issue - I’m thinking it can wait until meteor supports an async start. What do you think? |
|
@perbergland Absolutely! It can wait. It's just I came across the core meteor PR you had just made and figured how I could help you out. |
|
I will deploy a new Meteor 3.4.1-beta so that this can advance. meteor/meteor#14317 / Looks good. |
|
Merging this here, #196 To fix the tests and provide a beta version to test the fix on the core: meteor/meteor#14405 and meteor/meteor#14396 |
d8ceea5
into
Meteor-Community-Packages:release/3.4.0
Summary
Fixes #176
mocha.run()can fire before allMeteor.startup()callbacks have completed, due to async startup callbacks and top-levelawait(TLA) in the module systemMeteor.startup(resolve)promise at the start of thestart()function, ensuring all previously queued startup callbacks (including async ones) have finished before tests beginHow it works
Meteor.startup()runs callbacks in FIFO order. By the time the test driver'sstart()is called, all app code has been loaded and has registered its startup callbacks. Adding one more callback (resolve) at the end of the queue guarantees it runs last, after all prior async startup work has drained.Change
package/server.js: Madestart()async and addedawait new Promise(resolve => Meteor.startup(resolve))before test execution.Test results
Tested with the dummy app on Meteor 3.3.2, server-only (
TEST_CLIENT=0).With the fix — all 4 tests pass:
Without the fix — the async startup test fails, confirming the regression test is reliable:
Test plan
meteor testwith asyncMeteor.startup()callbacks — passes with fix, fails withoutmeteor test --full-app— same behaviorTEST_WATCH=1(watch mode) still worksTEST_PARALLEL=1still works🤖 Generated with Claude Code