Testing Asynchronicity and Signals
fakeAsync and tick
1import { fakeAsync, tick, flush, flushMicrotasks } from '@angular/core/testing';
2
3describe('AsyncComponent', () => {
4 it('should update after delay - fakeAsync', fakeAsync(() => {
5 const component = new AsyncComponent();
6
7 component.loadData(); // Sets setTimeout for 1000ms
8
9 expect(component.data).toBeNull();
10
11 tick(500); // Advance time by 500ms
12 expect(component.data).toBeNull();
13
14 tick(500); // Another 500ms
15 expect(component.data).toBeTruthy();
16 }));
17
18 it('should handle multiple async operations', fakeAsync(() => {
19 const component = new AsyncComponent();
20
21 component.loadMultiple(); // Multiple setTimeouts
22
23 flush(); // Execute all timers
24
25 expect(component.allLoaded).toBeTrue();
26 }));
27
28 it('should handle promises', fakeAsync(() => {
29 const component = new AsyncComponent();
30
31 component.loadWithPromise();
32
33 flushMicrotasks(); // Resolves Promise
34
35 expect(component.promiseResult).toBeDefined();
36 }));
37});
waitForAsync
1import { waitForAsync } from '@angular/core/testing';
2
3describe('AsyncHttpComponent', () => {
4 it('should fetch data asynchronously', waitForAsync(() => {
5 const fixture = TestBed.createComponent(AsyncHttpComponent);
6 const component = fixture.componentInstance;
7
8 component.fetchData();
9
10 fixture.whenStable().then(() => {
11 fixture.detectChanges();
12 expect(component.data).toBeTruthy();
13 });
14 }));
15});
Testing Signals
1import { signal, computed, effect } from '@angular/core';
2import { TestBed } from '@angular/core/testing';
3
4describe('SignalStore', () => {
5 it('should update signal value', () => {
6 const count = signal(0);
7
8 count.set(5);
9 expect(count()).toBe(5);
10
11 count.update(v => v + 1);
12 expect(count()).toBe(6);
13 });
14
15 it('should compute derived values', () => {
16 const count = signal(10);
17 const doubled = computed(() => count() * 2);
18
19 expect(doubled()).toBe(20);
20
21 count.set(5);
22 expect(doubled()).toBe(10);
23 });
24});
25
26describe('ComponentWithSignals', () => {
27 let component: SignalComponent;
28 let fixture: ComponentFixture<SignalComponent>;
29
30 beforeEach(async () => {
31 await TestBed.configureTestingModule({
32 imports: [SignalComponent]
33 }).compileComponents();
34
35 fixture = TestBed.createComponent(SignalComponent);
36 component = fixture.componentInstance;
37 fixture.detectChanges();
38 });
39
40 it('should update template when signal changes', () => {
41 component.count.set(42);
42 fixture.detectChanges();
43
44 const compiled = fixture.nativeElement;
45 expect(compiled.querySelector('.count').textContent).toContain('42');
46 });
47
48 it('should react to input signal changes', () => {
49 // For input() signals use setInput
50 fixture.componentRef.setInput('name', 'Musashi');
51 fixture.detectChanges();
52
53 expect(fixture.nativeElement.textContent).toContain('Musashi');
54 });
55});
Testing Observables with Marbles (optional)
1import { TestScheduler } from 'rxjs/testing';
2
3describe('RxJS Marble Testing', () => {
4 let scheduler: TestScheduler;
5
6 beforeEach(() => {
7 scheduler = new TestScheduler((actual, expected) => {
8 expect(actual).toEqual(expected);
9 });
10 });
11
12 it('should filter even numbers', () => {
13 scheduler.run(({ cold, expectObservable }) => {
14 const source$ = cold('a-b-c-d-e|', { a: 1, b: 2, c: 3, d: 4, e: 5 });
15 const expected = '--b---d--|';
16
17 const result$ = source$.pipe(filter(n => n % 2 === 0));
18
19 expectObservable(result$).toBe(expected, { b: 2, d: 4 });
20 });
21 });
22});
Testing Effects
1describe('Effect Testing', () => {
2 it('should trigger effect on signal change', () => {
3 let effectCount = 0;
4
5 TestBed.runInInjectionContext(() => {
6 const count = signal(0);
7
8 effect(() => {
9 count(); // Read signal in effect
10 effectCount++;
11 });
12
13 // First effect run
14 TestBed.flushEffects();
15 expect(effectCount).toBe(1);
16
17 // Signal change
18 count.set(1);
19 TestBed.flushEffects();
20 expect(effectCount).toBe(2);
21 });
22 });
23});