Skip to content

fix: await Meteor.startup() queue before running tests - #177

Merged
nachocodoner merged 2 commits into
Meteor-Community-Packages:release/3.4.0from
perbergland:fix/await-startup-before-tests
May 11, 2026
Merged

fix: await Meteor.startup() queue before running tests#177
nachocodoner merged 2 commits into
Meteor-Community-Packages:release/3.4.0from
perbergland:fix/await-startup-before-tests

Conversation

@perbergland

@perbergland perbergland commented Feb 18, 2026

Copy link
Copy Markdown

Summary

Fixes #176

  • In Meteor 3.x, mocha.run() can fire before all Meteor.startup() callbacks have completed, due to async startup callbacks and top-level await (TLA) in the module system
  • This causes flaky/failing tests when server initialization depends on async startup work (e.g. ensuring MongoDB indices, initializing collections)
  • The fix awaits a Meteor.startup(resolve) promise at the start of the start() function, ensuring all previously queued startup callbacks (including async ones) have finished before tests begin

How it works

Meteor.startup() runs callbacks in FIFO order. By the time the test driver's start() 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: Made start() async and added await 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:

  async Meteor.startup()
    ✔ should complete before tests run

  server suite
    ✔ passing test
    ✔ passing async test
    ✔ passing callback test

  4 passing (3ms)

Without the fix — the async startup test fails, confirming the regression test is reliable:

  async Meteor.startup()
    1) should complete before tests run

  server suite
    ✔ passing test
    ✔ passing async test
    ✔ passing callback test

  3 passing (4ms)
  1 failing

  1) async Meteor.startup()
       should complete before tests run:
     AssertionError: Async Meteor.startup() callback did not complete
     before tests ran.

Test plan

  • Run meteor test with async Meteor.startup() callbacks — passes with fix, fails without
  • Run meteor test --full-app — same behavior
  • Verify TEST_WATCH=1 (watch mode) still works
  • Verify TEST_PARALLEL=1 still works

🤖 Generated with Claude Code

perbergland and others added 2 commits February 18, 2026 14:25
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>
Comment thread package/server.js

// Before Meteor calls the `start` function, app tests will be parsed and loaded by Mocha
function start() {
async function start() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
    });
}

Comment thread package/server.js
// 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));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@perbergland

Copy link
Copy Markdown
Author

Thanks for the review @italojs — you're right that Meteor doesn't await start(). I've opened a companion PR on Meteor itself to fix this at the call site:

That makes the Meteor.startup callback async and awaits testDriverPackage.start(), so test drivers can safely use async/await.

In the meantime, I'll update this PR to use the .then()/.catch() pattern you suggested so it works regardless of whether the Meteor-side fix lands.

@harryadel

Copy link
Copy Markdown
Member

@perbergland Once you update this PR, ping me so we can move it forward! Thanks!

@perbergland

Copy link
Copy Markdown
Author

This is such an old issue - I’m thinking it can wait until meteor supports an async start. What do you think?

@harryadel

Copy link
Copy Markdown
Member

@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.

@nachocodoner

Copy link
Copy Markdown
Member

I will deploy a new Meteor 3.4.1-beta so that this can advance.

meteor/meteor#14317 / Looks good.

@nachocodoner

Copy link
Copy Markdown
Member

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

@nachocodoner
nachocodoner merged commit d8ceea5 into Meteor-Community-Packages:release/3.4.0 May 11, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tests run before async Meteor.startup() callbacks complete (TLA / Meteor 3 timing)

4 participants