Execution context library that persists across asynchronous operations for JavaScript applications
Comprehensive testing framework with fakeAsync, async testing zones, and task tracking for testing asynchronous code in a controlled environment.
Testing utilities that allow control of time and microtask execution for deterministic async testing.
/**
* Create a fakeAsync test function that controls time
* @param fn - Test function to wrap
* @param options - Configuration options
* @returns Wrapped test function
*/
function fakeAsync(fn: Function, options?: {flush?: boolean}): (...args: any[]) => any;
/**
* Advance virtual time by specified milliseconds
* @param millis - Milliseconds to advance (default: 0)
* @param ignoreNestedTimeout - Whether to ignore nested setTimeout calls
*/
function tick(millis?: number, ignoreNestedTimeout?: boolean): void;
/**
* Flush all pending macrotasks and microtasks
* @param maxTurns - Maximum number of task execution cycles
* @returns Number of tasks flushed
*/
function flush(maxTurns?: number): number;
/**
* Flush only pending microtasks
*/
function flushMicrotasks(): void;
/**
* Discard all pending periodic tasks (setInterval)
*/
function discardPeriodicTasks(): void;
/**
* Reset the fakeAsync zone to initial state
*/
function resetFakeAsyncZone(): void;
/**
* Wrap a function to automatically use ProxyZoneSpec for test isolation
* @param fn - Function to wrap with proxy zone behavior
* @returns Wrapped function that runs in proxy zone context
*/
function withProxyZone<T extends Function>(fn: T): T;Usage Examples:
import 'zone.js/testing';
describe('FakeAsync Tests', () => {
it('should control time', fakeAsync(() => {
let executed = false;
setTimeout(() => {
executed = true;
}, 1000);
// Time hasn't advanced yet
expect(executed).toBe(false);
// Advance time by 1000ms
tick(1000);
// Now the timeout has executed
expect(executed).toBe(true);
}));
it('should handle promises', fakeAsync(() => {
let result = '';
Promise.resolve('async-value')
.then(value => result = value);
// Promise hasn't resolved yet
expect(result).toBe('');
// Flush microtasks
flushMicrotasks();
// Promise has resolved
expect(result).toBe('async-value');
}));
it('should handle mixed async operations', fakeAsync(() => {
const results: string[] = [];
// Schedule various async operations
setTimeout(() => results.push('timeout-100'), 100);
setTimeout(() => results.push('timeout-200'), 200);
Promise.resolve().then(() => results.push('promise-1'));
Promise.resolve().then(() => results.push('promise-2'));
setInterval(() => results.push('interval'), 50);
// Flush microtasks first
flushMicrotasks();
expect(results).toEqual(['promise-1', 'promise-2']);
// Advance time and check results
tick(50);
expect(results).toContain('interval');
tick(50); // Total: 100ms
expect(results).toContain('timeout-100');
tick(100); // Total: 200ms
expect(results).toContain('timeout-200');
// Clean up periodic tasks
discardPeriodicTasks();
}));
it('should use proxy zone for test isolation', withProxyZone(() => {
let result = '';
// This test runs in an isolated proxy zone
setTimeout(() => {
result = 'completed';
}, 100);
tick(100);
expect(result).toBe('completed');
// ProxyZoneSpec automatically handles cleanup
}));
});Zone specification for fakeAsync testing environment.
/**
* Zone specification for controlling time in tests
*/
class FakeAsyncTestZoneSpec implements ZoneSpec {
/** Zone name identifier */
name: 'fakeAsync';
/**
* Advance virtual time by specified milliseconds
* @param millis - Milliseconds to advance
* @param doTick - Optional callback for each tick
*/
tick(millis?: number, doTick?: (elapsed: number) => void): void;
/**
* Flush all pending tasks
* @param maxTurns - Maximum execution cycles
* @returns Number of tasks flushed
*/
flush(maxTurns?: number): number;
/**
* Flush only microtasks
*/
flushMicrotasks(): void;
/**
* Get current fake system time
* @returns Current virtual time in milliseconds
*/
getFakeSystemTime(): number;
}Usage Examples:
import 'zone.js/testing';
// Direct use of FakeAsyncTestZoneSpec
const fakeAsyncZone = Zone.current.fork(new FakeAsyncTestZoneSpec());
fakeAsyncZone.run(() => {
let value = 0;
setTimeout(() => value = 1, 100);
setTimeout(() => value = 2, 200);
// Control time directly on the zone spec
const zoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
zoneSpec.tick(100);
expect(value).toBe(1);
zoneSpec.tick(100); // Total: 200ms
expect(value).toBe(2);
});Testing utilities for asynchronous operations that need to complete naturally.
/**
* Zone specification for async testing
*/
class AsyncTestZoneSpec implements ZoneSpec {
/** Zone name identifier */
name: 'async-test';
/**
* Wait for all pending asynchronous operations to complete
* @returns Promise that resolves when all async operations are done
*/
whenStable(): Promise<any>;
/**
* Check if there are any pending asynchronous operations
* @returns true if async operations are pending
*/
hasPendingAsyncTasks(): boolean;
/**
* Check if there are any pending microtasks
* @returns true if microtasks are pending
*/
hasPendingMicrotasks(): boolean;
/**
* Check if there are any pending macrotasks
* @returns true if macrotasks are pending
*/
hasPendingMacrotasks(): boolean;
}Usage Examples:
import 'zone.js/testing';
describe('Async Tests', () => {
let asyncZone: Zone;
let asyncZoneSpec: AsyncTestZoneSpec;
beforeEach(() => {
asyncZoneSpec = new AsyncTestZoneSpec();
asyncZone = Zone.current.fork(asyncZoneSpec);
});
it('should wait for async operations', async () => {
let completed = false;
asyncZone.run(() => {
setTimeout(() => {
completed = true;
}, 100);
fetch('/api/data').then(() => {
// Some async operation
});
});
// Wait for all async operations to complete
await asyncZoneSpec.whenStable();
expect(completed).toBe(true);
});
it('should check pending tasks', () => {
asyncZone.run(() => {
setTimeout(() => {}, 100);
expect(asyncZoneSpec.hasPendingMacrotasks()).toBe(true);
expect(asyncZoneSpec.hasPendingAsyncTasks()).toBe(true);
});
});
});Zone specification for monitoring and tracking task execution.
/**
* Zone specification for tracking task execution
*/
class TaskTrackingZoneSpec implements ZoneSpec {
/** Zone name identifier */
name: 'task-tracking';
/** Array of scheduled microtasks */
readonly microTasks: Task[];
/** Array of scheduled macrotasks */
readonly macroTasks: Task[];
/** Array of scheduled event tasks */
readonly eventTasks: Task[];
/**
* Clear all tracked tasks
*/
clear(): void;
/**
* Get tasks by type
* @param type - Task type to filter by
* @returns Array of tasks of the specified type
*/
getTasksByType(type: TaskType): Task[];
/**
* Get tasks by source
* @param source - Source identifier to filter by
* @returns Array of tasks from the specified source
*/
getTasksBySource(source: string): Task[];
}Usage Examples:
import 'zone.js/testing';
describe('Task Tracking Tests', () => {
let trackingZone: Zone;
let trackingSpec: TaskTrackingZoneSpec;
beforeEach(() => {
trackingSpec = new TaskTrackingZoneSpec();
trackingZone = Zone.current.fork(trackingSpec);
});
it('should track scheduled tasks', () => {
trackingZone.run(() => {
setTimeout(() => {}, 100);
setTimeout(() => {}, 200);
Promise.resolve().then(() => {});
document.addEventListener('click', () => {});
});
expect(trackingSpec.macroTasks).toHaveLength(2);
expect(trackingSpec.microTasks).toHaveLength(1);
expect(trackingSpec.eventTasks).toHaveLength(1);
// Check specific task sources
const timerTasks = trackingSpec.getTasksBySource('setTimeout');
expect(timerTasks).toHaveLength(2);
const promiseTasks = trackingSpec.getTasksByType('microTask');
expect(promiseTasks).toHaveLength(1);
});
it('should clear tracked tasks', () => {
trackingZone.run(() => {
setTimeout(() => {}, 100);
Promise.resolve().then(() => {});
});
expect(trackingSpec.macroTasks).toHaveLength(1);
expect(trackingSpec.microTasks).toHaveLength(1);
trackingSpec.clear();
expect(trackingSpec.macroTasks).toHaveLength(0);
expect(trackingSpec.microTasks).toHaveLength(0);
});
});Zone specification for test isolation and delegation control.
/**
* Zone specification for test isolation and delegation
*/
class ProxyZoneSpec implements ZoneSpec {
/** Zone name identifier */
name: 'proxy';
/**
* Set the delegate zone specification
* @param delegateSpec - Zone spec to delegate operations to
*/
setDelegate(delegateSpec: ZoneSpec): void;
/**
* Get the current delegate zone specification
* @returns Current delegate zone spec
*/
getDelegate(): ZoneSpec;
/**
* Reset delegation to default behavior
*/
resetDelegate(): void;
/**
* Assert that no tasks are pending
* @param ignoreLeisure - Whether to ignore leisure tasks
*/
assertNoPendingTasks(ignoreLeisure?: boolean): void;
}Usage Examples:
import 'zone.js/testing';
describe('Proxy Zone Tests', () => {
let proxyZone: Zone;
let proxySpec: ProxyZoneSpec;
beforeEach(() => {
proxySpec = new ProxyZoneSpec();
proxyZone = Zone.current.fork(proxySpec);
});
it('should isolate tests', () => {
// Set up custom delegate for this test
const customDelegate = {
name: 'custom-test',
onHandleError: (delegate, current, target, error) => {
console.log('Custom error handling:', error);
return true; // Handle the error
}
};
proxySpec.setDelegate(customDelegate);
proxyZone.run(() => {
// Test code that might throw errors
throw new Error('Test error');
// Error will be handled by custom delegate
});
// Reset for next test
proxySpec.resetDelegate();
});
it('should assert no pending tasks', () => {
proxyZone.run(() => {
// Synchronous operations only
const result = 1 + 1;
expect(result).toBe(2);
});
// Should not throw - no pending tasks
proxySpec.assertNoPendingTasks();
});
it('should detect pending tasks', () => {
proxyZone.run(() => {
setTimeout(() => {}, 100);
});
// Should throw - there are pending tasks
expect(() => {
proxySpec.assertNoPendingTasks();
}).toThrow();
});
});Zone specification for enhanced error stack traces across async boundaries.
/**
* Enable long stack traces across async boundaries
* @param Zone - Zone constructor
*/
function patchLongStackTrace(Zone: ZoneType): void;
/**
* Zone specification for enhanced stack traces
*/
class LongStackTraceZoneSpec implements ZoneSpec {
/** Zone name identifier */
name: 'long-stack-trace';
/**
* Get enhanced stack trace for an error
* @param error - Error to get stack trace for
* @returns Enhanced stack trace string
*/
getLongStackTrace(error: Error): string;
}Usage Examples:
import 'zone.js/testing';
// Enable long stack traces
Zone.__load_patch('longStackTraceZone', (global, Zone) => {
patchLongStackTrace(Zone);
});
const longStackZone = Zone.current.fork(new LongStackTraceZoneSpec());
longStackZone.run(() => {
function asyncFunction() {
return new Promise((resolve, reject) => {
setTimeout(() => {
someOtherAsyncFunction()
.then(resolve)
.catch(reject);
}, 100);
});
}
function someOtherAsyncFunction() {
return Promise.reject(new Error('Async error'));
}
asyncFunction().catch(error => {
// Error will have enhanced stack trace showing
// the full async call chain
console.log(error.stack);
});
});Install with Tessl CLI
npx tessl i tessl/npm-zone-js