Emberjs
Testing
Promises
Async
JavaScript

Emberjs - How to test promises and other async behavior?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Testing async behavior in Ember is much easier once you stop thinking in terms of manual promise plumbing and start using Ember’s test helpers correctly. Modern Ember testing is built around async and await, plus helpers that already know how to wait for rendering, timers, promises, and request-driven UI updates to settle.

Use Async Helpers Instead of Manual Timing

The main rule is simple: do not test async code with arbitrary delays such as setTimeout(..., 50). Ember’s @ember/test-helpers package already provides helpers that wait for the app to settle after interactions.

A rendering test might look like this:

javascript
1import { module, test } from 'qunit';
2import { setupRenderingTest } from 'ember-qunit';
3import { render, click, settled } from '@ember/test-helpers';
4import { hbs } from 'ember-cli-htmlbars';
5
6module('Integration | Component | save-button', function(hooks) {
7  setupRenderingTest(hooks);
8
9  test('it shows a success message after async save', async function(assert) {
10    this.set('savePost', async () => {
11      await Promise.resolve();
12      this.set('message', 'Saved');
13    });
14
15    await render(hbs`
16      <SaveButton @onSave={{this.savePost}} @message={{this.message}} />
17    `);
18
19    await click('button');
20    await settled();
21
22    assert.dom('.status').hasText('Saved');
23  });
24});

click() already waits for the async work it triggers, and settled() is available when you want to be explicit.

Test the User-Visible Result, Not the Promise Itself

A common mistake is writing assertions about internal promise state. In Ember, the stronger test is usually about the DOM or public state after the async work completes.

For example, if a route loads data:

javascript
1import { module, test } from 'qunit';
2import { setupApplicationTest } from 'ember-qunit';
3import { visit } from '@ember/test-helpers';
4
5module('Acceptance | posts', function(hooks) {
6  setupApplicationTest(hooks);
7
8  test('it renders posts after loading', async function(assert) {
9    await visit('/posts');
10    assert.dom('[data-test-post]').exists({ count: 3 });
11  });
12});

This is better than checking whether some route method returned a promise, because it verifies behavior the user actually depends on.

Waiting for Non-Standard Async Work

Sometimes the built-in settled state is not enough. For example, a third-party library may update state outside Ember’s usual tracking path. In those cases, use waitUntil or waitFor.

javascript
1import { waitUntil } from '@ember/test-helpers';
2
3await waitUntil(() => this.status === 'ready');
4assert.strictEqual(this.status, 'ready');

This is still better than sleeping for a guessed number of milliseconds. You wait for a real condition instead of hoping timing lines up.

Mock Network Requests

Async tests become reliable when the network is under your control. In Ember apps, Mirage is a common choice for that. A basic test could look like this:

javascript
1test('it renders fetched users', async function(assert) {
2  this.server.get('/api/users', () => {
3    return {
4      users: [
5        { id: 1, name: 'Ada' },
6        { id: 2, name: 'Grace' }
7      ]
8    };
9  });
10
11  await visit('/users');
12
13  assert.dom('[data-test-user]').exists({ count: 2 });
14  assert.dom('[data-test-user="Ada"]').exists();
15});

Now the test is deterministic because the response shape and timing are defined by the test.

Older Patterns Versus Current Patterns

If you find examples using andThen, run, or deeply nested callbacks, check the Ember version before copying them. Older testing styles existed before the current async-helper model was standardized. In current Ember code, async and await with @ember/test-helpers are the baseline.

That shift matters because old tests often become flaky when mixed with modern helpers. Stay within one testing style.

Common Pitfalls

The most common mistake is using manual delays instead of waiting on a condition or using Ember’s async helpers. That creates flaky tests that pass locally and fail in CI.

Another issue is forgetting to await helper calls. If you write click('button') without await, the next assertion may run before the UI updates.

Tests also become brittle when they assert implementation details instead of observable results. Prefer checking rendered text, element presence, or tracked state exposed through the component.

Finally, if an async flow depends on HTTP, mock it. Real network traffic makes tests slower and less predictable.

Summary

  • Modern Ember async tests should use async and await with @ember/test-helpers.
  • Helpers such as visit, render, and click already wait for async work they trigger.
  • Use settled, waitUntil, or waitFor when you need explicit control.
  • Test user-visible outcomes rather than internal promise objects.
  • Avoid manual delays and old callback-heavy testing patterns.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.