Outcome Recipes
Advanced testing patterns for complex scenarios including async operations, routing, service mocking, and end-to-end component interaction testing.
These recipes show how to test complex real-world scenarios in Aurelia applications. Use them when basic component testing isn't enough.
1. Testing components with async API calls
Goal: Test components that load data from APIs with proper handling of loading states, successful responses, and error scenarios.
Steps
Create a mock HTTP client with configurable responses:
import { IHttpClient } from '@aurelia/fetch-client'; import { Registration } from '@aurelia/kernel'; export class MockHttpClient implements IHttpClient { private mockResponses = new Map<string, any>(); private mockErrors = new Map<string, Error>(); baseUrl = ''; activeRequestCount = 0; isRequesting = false; configure() { return this; } setMockResponse(url: string, data: any, status: number = 200) { this.mockResponses.set(url, { data, status }); } setMockError(url: string, error: Error) { this.mockErrors.set(url, error); } async fetch(input: RequestInfo | Request): Promise<Response> { const url = typeof input === 'string' ? input : input.url; if (this.mockErrors.has(url)) { throw this.mockErrors.get(url); } const mock = this.mockResponses.get(url); if (!mock) { throw new Error(`No mock response configured for ${url}`); } return new Response(JSON.stringify(mock.data), { status: mock.status, headers: { 'Content-Type': 'application/json' } }); } } export const MockHttpClientRegistration = Registration.instance( IHttpClient, new MockHttpClient() );Test component with successful data loading:
import { createFixture } from '@aurelia/testing'; import { MockHttpClient, MockHttpClientRegistration } from './mock-http-client'; import { ProductList } from './product-list'; describe('ProductList', () => { it('should load and display products', async () => { const mockHttp = new MockHttpClient(); mockHttp.setMockResponse('/api/products', { products: [ { id: '1', name: 'Product 1', price: 10 }, { id: '2', name: 'Product 2', price: 20 } ] }); const { component, assertText, platform } = await createFixture .component(ProductList) .html`<div> <div if.bind="loading">Loading...</div> <div if.bind="error">\${error}</div> <div repeat.for="product of products">\${product.name}</div> </div>` .deps(Registration.instance(IHttpClient, mockHttp)) .build() .started; // Initial loading state assertText('Loading...'); // Wait for async attached() to complete await tasksSettled(); // Verify products are displayed assertText('Product 1Product 2'); expect(component.loading).toBe(false); expect(component.products.length).toBe(2); await fixture.stop(true); }); });Test error handling:
it('should display error message when API fails', async () => { const mockHttp = new MockHttpClient(); mockHttp.setMockError('/api/products', new Error('Network error')); const { component, assertText, platform } = await createFixture .component(ProductList) .html`<div> <div if.bind="loading">Loading...</div> <div if.bind="error">\${error}</div> <div repeat.for="product of products">\${product.name}</div> </div>` .deps(Registration.instance(IHttpClient, mockHttp)) .build() .started; // Wait for async operation await tasksSettled(); // Verify error is displayed expect(component.error).toBeTruthy(); expect(component.products.length).toBe(0); expect(component.loading).toBe(false); await fixture.stop(true); });Test retry functionality:
it('should retry loading when retry button is clicked', async () => { const mockHttp = new MockHttpClient(); let callCount = 0; // First call fails, second succeeds mockHttp.fetch = async (input: RequestInfo | Request) => { callCount++; if (callCount === 1) { throw new Error('Temporary error'); } return new Response(JSON.stringify({ products: [{ id: '1', name: 'Product 1', price: 10 }] }), { status: 200, headers: { 'Content-Type': 'application/json' } }); }; const { component, trigger, assertText, platform } = await createFixture .component(ProductList) .html`<div> <div if.bind="loading">Loading...</div> <div if.bind="error"> \${error} <button click.trigger="retry()">Retry</button> </div> <div repeat.for="product of products">\${product.name}</div> </div>` .deps(Registration.instance(IHttpClient, mockHttp)) .build() .started; // Wait for initial failed load await tasksSettled(); expect(component.error).toBeTruthy(); // Click retry button trigger.click('button'); // Wait for retry to complete await tasksSettled(); // Verify success assertText('Product 1'); expect(component.error).toBeNull(); expect(callCount).toBe(2); await fixture.stop(true); });
Checklist
Loading state displays before data arrives
Successful API calls populate component data
Error states are handled and displayed
Retry functionality reloads data
All async operations can be waited using
await tasksSettled()for timing
2. Testing router navigation and route parameters
Goal: Test components that use routing for navigation, parameter extraction, and route guards.
Steps
Set up a test with router configuration:
Test route parameter extraction:
Test route guards:
Test route guard redirects:
Checklist
Router navigation works in tests
Route parameters are extracted correctly
canLoadguards are invoked and respectedRedirects from guards work as expected
Current route state is verifiable
3. Testing with validation
Goal: Test form validation including rules, error display, and submission prevention.
Steps
Set up validation testing environment:
Test validation with valid input:
Test field-level validation on blur:
Checklist
Validation rules are checked on submit
Invalid data prevents form submission
Valid data passes validation
Field-level validation triggers on blur
Error messages are accessible via controller
4. Testing complex component interactions
Goal: Test parent-child component communication, custom events, and state synchronization.
Steps
Test parent-child data binding:
Test child-to-parent communication via custom events:
Test sibling component communication via shared service:
Checklist
Parent-to-child data flows via
@bindableChild-to-parent communication works via events
Sibling components share state via services
Changes propagate correctly across component tree
Custom events are dispatched and handled
5. Testing lifecycle hooks in complex scenarios
Goal: Test components with complex initialization, async lifecycle hooks, and cleanup operations.
Steps
Test async data loading in lifecycle hooks:
Test cleanup in detaching/unbinding:
Test lifecycle hook order:
Checklist
Async lifecycle hooks complete before component is ready
Cleanup hooks properly dispose of resources
Hook execution order is correct
.startedwaits for all async hooks to completestop(true)triggers cleanup hooks
6. Testing with real-world dependencies
Goal: Test components that depend on multiple services, handle complex state, and integrate with external systems.
Steps
Create comprehensive mocks for service dependencies:
Test component with multiple service dependencies:
Test error scenarios and recovery:
Checklist
Multiple service dependencies are properly mocked
Service interactions are tested in integration
Error scenarios are tested and handled
Retry/recovery mechanisms work correctly
Mock services provide realistic behavior
Testing pattern cheat sheet
Async API calls
Mock HTTP client + await tasksSettled()
MockHttpClient, await tasksSettled()
Router navigation
Router configuration + router.load()
RouterConfiguration, IRouter
Form validation
Validation rules + controller
IValidationRules, IValidationController
Component interaction
Bindables + custom events
@bindable, CustomEvent
Lifecycle hooks
Hook execution + timing
.started, stop(true)
Service dependencies
Mock registrations
Registration.instance(), mock services
Best practices
Always await
.started: Ensures all async lifecycle hooks completeUse
await tasksSettled(): After async operations or state changesMock external dependencies: HTTP clients, auth services, APIs
Test error paths: Don't just test happy scenarios
Clean up with
stop(true): Prevents memory leaks and interferenceIsolate tests: Each test should be independent
Use descriptive test names: Clear "should..." statements
Test user interactions: Clicks, typing, form submission
Verify state changes: Check both component properties and DOM
Test accessibility: Verify ARIA attributes and keyboard navigation
See also
Testing Components - Basic component testing
Testing Quick Reference - Common testing patterns
Mocks and Spies - Creating test doubles
Advanced Testing - Advanced techniques
Fluent API - Fixture builder API reference
Last updated
Was this helpful?