0
0
AngularHow-ToBeginner · 4 min read

How to Use Async in Angular Test: Simple Guide

In Angular tests, use the async utility from @angular/core/testing to wrap asynchronous test code, allowing Angular to wait for async operations like promises or timers to complete before asserting. Combine async with fixture.whenStable() or fakeAsync and tick() for precise control over async behavior in tests.
📐

Syntax

The async function wraps your test function to handle asynchronous operations. Inside it, Angular waits for all pending promises and timers to complete before continuing.

Use fixture.whenStable() to wait for async tasks in the component, and fakeAsync with tick() to simulate time passing.

typescript
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

it('should test async code', async(() => {
  // Arrange
  // Act
  // Assert after async tasks complete
}));
💻

Example

This example shows how to test a component method that updates a value after a promise resolves using async and fixture.whenStable().

typescript
import { Component } from '@angular/core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';

@Component({
  selector: 'app-async-test',
  template: `<p>{{message}}</p>`
})
class AsyncTestComponent {
  message = 'Initial';

  updateMessage() {
    return new Promise<void>((resolve) => {
      setTimeout(() => {
        this.message = 'Updated';
        resolve();
      }, 1000);
    });
  }
}

describe('AsyncTestComponent', () => {
  let fixture: ComponentFixture<AsyncTestComponent>;
  let component: AsyncTestComponent;

  beforeEach(() => {
    TestBed.configureTestingModule({ declarations: [AsyncTestComponent] });
    fixture = TestBed.createComponent(AsyncTestComponent);
    component = fixture.componentInstance;
  });

  it('should update message after async call', async(() => {
    component.updateMessage();
    fixture.detectChanges();

    fixture.whenStable().then(() => {
      fixture.detectChanges();
      expect(component.message).toBe('Updated');
      const compiled = fixture.nativeElement as HTMLElement;
      expect(compiled.querySelector('p')?.textContent).toBe('Updated');
    });
  }));
});
Output
Test passes confirming message updates to 'Updated' after async operation.
⚠️

Common Pitfalls

  • Not using async or fakeAsync causes tests to finish before async code runs, leading to false positives.
  • Forgetting to call fixture.detectChanges() after async completion means the template won't update in the test.
  • Mixing async and fakeAsync in the same test can cause errors; use one approach per test.
typescript
/* Wrong way: Missing async wrapper */
it('fails because async not handled', () => {
  component.updateMessage();
  fixture.detectChanges();
  expect(component.message).toBe('Updated'); // Fails because promise not resolved yet
});

/* Right way: Using async */
it('works with async', async(() => {
  component.updateMessage();
  fixture.detectChanges();
  fixture.whenStable().then(() => {
    fixture.detectChanges();
    expect(component.message).toBe('Updated');
  });
}));
📊

Quick Reference

FunctionPurposeUsage Example
asyncWraps test to wait for async tasksasync(() => { /* test code */ })
fixture.whenStable()Waits for async tasks in componentfixture.whenStable().then(() => { /* assertions */ })
fakeAsyncSimulates async with virtual timefakeAsync(() => { tick(1000); /* assertions */ })
tickAdvances virtual time in fakeAsynctick(500);

Key Takeaways

Always wrap async test code with Angular's async or fakeAsync utilities.
Use fixture.whenStable() to wait for promises and async tasks to finish before asserting.
Call fixture.detectChanges() after async completion to update the template.
Avoid mixing async and fakeAsync in the same test to prevent errors.
Use tick() inside fakeAsync to simulate time passing for timers.