Testing Signals and Async Code
Test reactive and asynchronous behavior.
Testing Signals and Async Code is a free Angular Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Angular Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Async Is the Hard Part of Testing
Real components do async work: timers, promises, HTTP, debounced inputs. Tests must control time so assertions run after the async work settles. Angular gives you fakeAsync, tick, and flush for deterministic time control.
Testing a Signal
A signal is read like a function. Testing it is straightforward: call the signal, assert the value; update it and assert again.
it('increments the count signal', () => {
const fixture = TestBed.createComponent(CounterComponent);
const c = fixture.componentInstance;
expect(c.count()).toBe(0);
c.increment();
expect(c.count()).toBe(1);
});Computed Signals Update Automatically
A computed recalculates when its dependencies change. In a test you change the source signal, then read the computed; no manual recompute needed.
expect(c.doubled()).toBe(0);
c.count.set(5);
expect(c.doubled()).toBe(10); // computed(() => c.count() * 2)Signals and the DOM
To see a signal change reflected in the template, call detectChanges() after updating it, just like any other state.
c.count.set(3);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('3');Introducing fakeAsync
Wrap a test in fakeAsync to enter a fake time zone. Timers and microtasks queue up instead of running, and you advance them manually. This makes async tests synchronous and reliable.
import { fakeAsync, tick } from '@angular/core/testing';
it('loads after delay', fakeAsync(() => {
// timers are now under your control
}));Advancing Time with tick
tick(ms) moves the virtual clock forward, running any timers scheduled within that window.
it('fires after 1s', fakeAsync(() => {
let done = false;
setTimeout(() => (done = true), 1000);
expect(done).toBe(false);
tick(1000);
expect(done).toBe(true);
}));flush for All Pending Timers
When you do not know the exact delay, flush() drains every pending macrotask until the queue is empty.
import { fakeAsync, flush } from '@angular/core/testing';
it('completes debounced search', fakeAsync(() => {
component.search('ang');
flush(); // run all timers (e.g. debounceTime)
expect(component.results().length).toBeGreaterThan(0);
}));Testing Promises with fakeAsync
Promises resolve as microtasks. Inside fakeAsync, call flushMicrotasks() (or tick()) to settle them before asserting.
import { fakeAsync, flushMicrotasks } from '@angular/core/testing';
it('resolves promise', fakeAsync(() => {
let v: number | undefined;
Promise.resolve(42).then((n) => (v = n));
flushMicrotasks();
expect(v).toBe(42);
}));The async / waitForAsync Helper
For tests that must wait on real async without controlling time, wrap the body in waitForAsync and use fixture.whenStable().
import { waitForAsync } from '@angular/core/testing';
it('settles', waitForAsync(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.componentInstance.ready()).toBe(true);
});
}));Testing effect()
An effect runs asynchronously after the signals it reads change. To observe its result, run change detection (or flush) so the effect executes, then assert the side effect.
c.theme.set('dark');
fixture.detectChanges(); // effect runs
expect(document.body.classList.contains('dark')).toBe(true);Common Pitfall: Leftover Timers
If a fakeAsync test ends with timers still queued, Angular throws an error. Always tick/flush them, or clear intervals in ngOnDestroy, so the virtual clock is empty when the test finishes.
Quick Check
Choose the right tool for a known delay.
Recap
Signals test like simple functions (call to read, set to update, detectChanges for the DOM). For async, fakeAsync gives a virtual clock you advance with tick, drain with flush, and settle promises with flushMicrotasks. Never leave timers pending.
Frequently asked questions
Is the “Testing Signals and Async Code” lesson free?
Yes — the full text of “Testing Signals and Async Code” is free to read here on the web, and the Angular Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Angular Academy course, upgrade to CoddyKit PRO.
What will I learn in “Testing Signals and Async Code”?
Test reactive and asynchronous behavior. You practise Angular Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Angular Academy?
No prior experience is required. Angular Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Testing Signals and Async Code” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Angular Academy lesson?
Yes. Every Angular Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- TestBed and Component Fixtures
- Testing Inputs, Outputs, and DOM
- Mocking Services and Dependencies
- Testing Signals and Async Code