Ember test with async await functions not working as intended

:wave: @mArtinko5MB, sorry for the confusion here!

tldr; add @ember/test-waiters to your application, and update your code snippet to:

import { waitFor } from '@ember/test-waiters';

// ...snip...

@action
@waitFor
async authenticate(e) {
  e.preventDefault();

  let { email, password } = this;
  try {
    await this.session.authenticate('authenticator:custom', email, password);
  } catch(error) {
  
    this.errorMessage = error.error || error;
  }
}

OK, so now that that is fixed, lets dig into the main issue you were running into: nothing in the system knows that it should “wait” for the async action to complete!

Lets break down what is waited for automatically by calls to await settled() (which is internally used by await click(...) as well). Settledness in this context is defined as:

  • Has no active runloop
  • Has no pending runloop timers (e.g. usages of schedule from @ember/runloop)
  • Has no pending test waiters (this one is important, we will come back to it)
  • Has no pending jQuery requests (generally not applicable in modern octane apps, since they do not use jQuery)
  • Has no pending router transitions (e.g. all model hooks are resolved, any redirects have been absorbed, etc)

One very important thing to call out here: we do not wait for “all promises to be resolved”! (Doing this is basically impossible anyways…)

OK, now back to your code snippet, in your test you are click(someSelector) that invokes an async action. Since the thing that action does does not fall into any of the “settledness delaying” categories, the test happily continues before that authentication is completed.

Now, lets get back to “has pending test waiters”. This is where @ember/test-waiters. @ember/test-waiters provides a few helpful APIs that make it very easy for us to indicate in our app (or addon) code that a given bit of async should be incorporated into a normal “settledness” check. The main building block that I would expect you to use here is waitFor (documentation here).

Using the decorator form of waitFor stacks quite nicely with @action and nicely absorbs all of the async going on in that action:

@action
@waitFor
async authenticate(e) {
  // ...snip...
}

Hope that helps!!

6 Likes