Angular2
async testing
unit testing
Jasmine
Angular testing

Angular2 async unit testing with jasmine

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Angular provides three utilities for testing asynchronous code with Jasmine: fakeAsync/tick (simulates time passage synchronously), waitForAsync (wraps the test in an async zone), and done callback (Jasmine's native async support). The most common and recommended approach is fakeAsync with tick because it gives you precise control over when async operations complete, making tests deterministic. For HTTP calls, use HttpClientTestingModule to mock requests and flush responses on demand.

typescript
1import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
2import { CounterComponent } from './counter.component';
3
4describe('CounterComponent', () => {
5  let component: CounterComponent;
6  let fixture: ComponentFixture<CounterComponent>;
7
8  beforeEach(() => {
9    TestBed.configureTestingModule({
10      declarations: [CounterComponent]
11    });
12    fixture = TestBed.createComponent(CounterComponent);
13    component = fixture.componentInstance;
14  });
15
16  it('should update value after debounce', fakeAsync(() => {
17    component.onInput('hello');
18
19    // No time has passed — debounced value not yet updated
20    expect(component.debouncedValue).toBe('');
21
22    // Simulate 300ms passing (the debounce time)
23    tick(300);
24
25    expect(component.debouncedValue).toBe('hello');
26  }));
27
28  it('should complete setTimeout', fakeAsync(() => {
29    let flag = false;
30    setTimeout(() => { flag = true; }, 1000);
31
32    expect(flag).toBe(false);
33    tick(1000);
34    expect(flag).toBe(true);
35  }));
36});

fakeAsync creates a synchronous test zone where all async operations (setTimeout, setInterval, Promises) are queued but not executed until you call tick(ms). This eliminates race conditions and flaky tests.

waitForAsync (formerly async)

typescript
1import { TestBed, waitForAsync } from '@angular/core/testing';
2import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3import { UserService } from './user.service';
4
5describe('UserService', () => {
6  let service: UserService;
7  let httpMock: HttpTestingController;
8
9  beforeEach(() => {
10    TestBed.configureTestingModule({
11      imports: [HttpClientTestingModule],
12      providers: [UserService]
13    });
14    service = TestBed.inject(UserService);
15    httpMock = TestBed.inject(HttpTestingController);
16  });
17
18  it('should fetch users', waitForAsync(() => {
19    service.getUsers().subscribe(users => {
20      expect(users.length).toBe(2);
21      expect(users[0].name).toBe('Alice');
22    });
23
24    const req = httpMock.expectOne('/api/users');
25    req.flush([{ name: 'Alice' }, { name: 'Bob' }]);
26  }));
27
28  afterEach(() => {
29    httpMock.verify();
30  });
31});

waitForAsync waits for all async operations in the Angular zone to complete before the test finishes. It is useful when you cannot easily control timing with tick.

Jasmine done Callback

typescript
1it('should resolve the promise', (done: DoneFn) => {
2  const promise = service.fetchData();
3
4  promise.then(result => {
5    expect(result).toBe('expected value');
6    done();  // Signal that the async test is complete
7  }).catch(done.fail);
8});

Jasmine's done callback is framework-agnostic. Call done() when the async operation completes. If you forget to call it, the test times out after 5 seconds (configurable with jasmine.DEFAULT_TIMEOUT_INTERVAL).

Testing Observable Streams

typescript
1import { fakeAsync, tick } from '@angular/core/testing';
2import { of, delay } from 'rxjs';
3
4it('should handle delayed observable', fakeAsync(() => {
5  let result: string | undefined;
6
7  of('hello').pipe(delay(500)).subscribe(val => {
8    result = val;
9  });
10
11  expect(result).toBeUndefined();
12  tick(500);
13  expect(result).toBe('hello');
14}));
typescript
1// Testing component that uses observables
2it('should display data after HTTP call', fakeAsync(() => {
3  fixture.detectChanges();  // Triggers ngOnInit
4
5  const req = httpMock.expectOne('/api/data');
6  req.flush({ message: 'Hello' });
7
8  tick();                    // Flush microtasks (Promise resolution)
9  fixture.detectChanges();   // Update the DOM
10
11  const element = fixture.nativeElement.querySelector('.message');
12  expect(element.textContent).toContain('Hello');
13}));

Testing with async/await

typescript
1it('should work with async/await', async () => {
2  const result = await service.fetchData();
3  expect(result).toBe('expected');
4});
5
6// Combining with Angular's testing utilities
7it('should render after async data loads', async () => {
8  fixture.detectChanges();
9  await fixture.whenStable();  // Wait for all async ops
10
11  const element = fixture.nativeElement.querySelector('.content');
12  expect(element).toBeTruthy();
13});

Native async/await works for simple Promise-based tests. For tests involving Angular zones, timers, or HTTP, prefer fakeAsync.

Testing Timers (setInterval)

typescript
1import { fakeAsync, tick, discardPeriodicTasks } from '@angular/core/testing';
2
3it('should poll every second', fakeAsync(() => {
4  let count = 0;
5  const interval = setInterval(() => count++, 1000);
6
7  tick(3000);
8  expect(count).toBe(3);
9
10  clearInterval(interval);
11  discardPeriodicTasks();  // Clean up remaining periodic tasks
12}));

discardPeriodicTasks() prevents the "periodic timers still in queue" error when the test ends with active intervals.

Common Pitfalls

  • Forgetting tick() in fakeAsync: Without tick(), setTimeout and Promise callbacks never execute. The test passes with stale values, hiding bugs. Always tick() the appropriate duration after triggering async operations.
  • Not calling fixture.detectChanges(): Angular does not automatically run change detection in tests. After async data arrives, call fixture.detectChanges() to update the DOM before asserting on rendered content.
  • Periodic tasks left in queue: If your component uses setInterval or RxJS interval, call discardPeriodicTasks() at the end of fakeAsync tests, or unsubscribe/clear the interval. Otherwise, Angular throws "periodic timer(s) still in the queue."
  • Mixing fakeAsync with real async: You cannot use real setTimeout or fetch inside fakeAsync — it intercepts them. If you need real HTTP calls (integration tests), use waitForAsync or done instead.
  • httpMock.verify() missing: Without afterEach(() => httpMock.verify()), unexpected HTTP requests silently pass. Always verify that no unmatched requests remain after each test.

Summary

  • Use fakeAsync + tick() for most async tests — it gives deterministic control over timing
  • Use waitForAsync when you need Angular zone-aware async completion without manual tick control
  • Use Jasmine's done callback for simple Promise-based tests outside Angular
  • Always call fixture.detectChanges() after async data arrives to update the template
  • Use HttpClientTestingModule to mock HTTP requests and httpMock.verify() to catch unexpected calls
  • Call discardPeriodicTasks() to clean up timers in fakeAsync tests

Course illustration
Course illustration

All Rights Reserved.