Skip to content

Testing Aurelia Applications with Unit and Integration Tests

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Testing Aurelia Applications with Unit and Integration Tests. We cover key concepts, practical examples, and best practices to help you master this topic.

Aurelia provides testing utilities that make it straightforward to unit test components in isolation and write integration tests that exercise the full framework lifecycle.

What You'll Learn

  • Setting up the Aurelia test harness
  • Unit testing services and ViewModels
  • Testing component rendering with the ComponentTester
  • Writing integration tests for routed applications
  • Mocking HTTP requests and dependencies

Why It Matters

Automated tests catch regressions early, document expected behavior, and give you confidence to refactor. Aurelia's testing helpers let you verify both logic and rendering without launching a browser manually.

Real-World Use

A continuous integration pipeline that runs hundreds of Aurelia component tests on every Pull Request, checking that form validation works, routes resolve correctly, and HTTP services handle errors properly.

Testing Strategy

flowchart TD
    A[Test Suite] --> B[Unit Tests]
    A --> C[Component Tests]
    A --> D[Integration Tests]
    B --> E[Service Classes]
    B --> F[Value Converters]
    B --> G[Custom Attributes]
    C --> H[ViewModel Logic]
    C --> I[Template Rendering]
    D --> J[Route Navigation]
    D --> K[Full App Workflows]
    style A fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px

Setting Up the Test Harness

Install the necessary packages:

npm install --save-dev @aurelia/testing jasmine karma karma-jasmine

Create a test setup file (test/setup.ts):

import 'aurelia-polyfills';
import { initialize } from 'aurelia-pal-browser';
import { DOM } from 'aurelia-pal';

initialize();

Unit Testing a Service

// src/services/math-service.ts
export class MathService {
  add(a: number, b: number): number {
    return a + b;
  }

  factorial(n: number): number {
    if (n < 0) throw new Error('Negative input');
    return n <= 1 ? 1 : n * this.factorial(n - 1);
  }
}

// test/services/math-service.spec.ts
import { MathService } from '../../src/services/math-service';

describe('MathService', () => {
  let service: MathService;

  beforeEach(() => {
    service = new MathService();
  });

  it('adds two numbers', () => {
    expect(service.add(2, 3)).toBe(5);
  });

  it('computes factorial of 5 as 120', () => {
    expect(service.factorial(5)).toBe(120);
  });

  it('throws for negative factorial input', () => {
    expect(() => service.factorial(-1)).toThrow();
  });
});

Expected output: All three tests pass: addition returns 5, factorial(5) returns 120, and negative input throws an error.

Unit Testing a ViewModel

// src/components/counter.ts
import { autoinject } from 'aurelia-framework';

@autoinject
export class Counter {
  count = 0;

  increment(): void {
    this.count++;
  }

  decrement(): void {
    if (this.count > 0) this.count--;
  }

  reset(): void {
    this.count = 0;
  }
}

// test/components/counter.spec.ts
import { Counter } from '../../src/components/counter';

describe('Counter ViewModel', () => {
  let counter: Counter;

  beforeEach(() => {
    counter = new Counter();
  });

  it('starts at 0', () => {
    expect(counter.count).toBe(0);
  });

  it('increments count by 1', () => {
    counter.increment();
    expect(counter.count).toBe(1);
  });

  it('does not decrement below 0', () => {
    counter.decrement();
    expect(counter.count).toBe(0);
  });

  it('resets to 0', () => {
    counter.increment();
    counter.increment();
    counter.reset();
    expect(counter.count).toBe(0);
  });
});

Expected output: All four tests pass, verifying the counter ViewModel logic in isolation from the template.

Component Testing with StageComponent

Use StageComponent to render a component and test its DOM:

import { StageComponent } from 'aurelia-testing';
import { bootstrap } from 'aurelia-bootstrapper';

describe('Counter Component', () => {
  let component;

  beforeEach(() => {
    component = StageComponent
      .withResources('components/counter')
      .inView('<counter></counter>')
      .boundTo({});
  });

  afterEach(() => {
    component.dispose();
  });

  it('renders the initial count', done => {
    component.create(bootstrap).then(() => {
      const element = document.querySelector('.counter-value');
      expect(element.textContent).toBe('0');
      done();
    });
  });

  it('updates when increment button is clicked', done => {
    component.create(bootstrap).then(() => {
      const button = document.querySelector('.increment-btn') as HTMLElement;
      button.click();
      const element = document.querySelector('.counter-value');
      expect(element.textContent).toBe('1');
      done();
    });
  });
});

Expected output: The component renders in a detached DOM, initial count is 0, and clicking the button updates the displayed value to 1.

Mocking HTTP Requests

// test/services/api-service.spec.ts
import { ApiService } from '../../src/services/api-service';
import { HttpClient } from 'aurelia-fetch-client';

describe('ApiService', () => {
  let service: ApiService;
  let http: jasmine.SpyObj<HttpClient>;

  beforeEach(() => {
    http = jasmine.createSpyObj('HttpClient', ['fetch']);
    service = new ApiService(http);
  });

  it('fetches users from the API', async () => {
    const mockUsers = [{ id: 1, name: 'Alice' }];
    http.fetch.and.returnValue(
      Promise.resolve({ json: () => Promise.resolve(mockUsers) })
    );

    const users = await service.getUsers();
    expect(users).toEqual(mockUsers);
    expect(http.fetch).toHaveBeenCalledWith('users');
  });

  it('handles fetch errors gracefully', async () => {
    http.fetch.and.returnValue(Promise.reject(new Error('Network error')));

    await expectAsync(service.getUsers()).toBeRejectedWithError('Network error');
  });
});

Expected output: The first test verifies that getUsers calls http.fetch('users') and returns the mocked JSON. The second test verifies that errors propagate correctly.

Testing Routed Components

import { Router } from 'aurelia-router';

describe('UserDetail Route', () => {
  let router: Router;

  beforeEach(() => {
    router = new Router();
    // Configure test routes
  });

  it('navigates to user detail with correct parameter', () => {
    spyOn(router, 'navigate');
    router.navigateToRoute('user-detail', { id: 42 });
    expect(router.navigate).toHaveBeenCalledWith('/users/42');
  });
});

Common Mistakes

  1. Not disposing the component after tests - StageComponent creates real DOM elements. Failing to call dispose() in afterEach causes memory leaks and test pollution.

  2. Forgetting to call done() in async tests - Aurelia's component creation is asynchronous. Always call done() or use async/await to prevent false passes.

  3. Testing implementation details instead of behavior - Test what the component does (renders data, responds to clicks), not how it does it (internal method calls).

  4. Not mocking HTTP dependencies - Real HTTP calls make tests slow, flaky, and dependent on network availability. Always mock the HttpClient.

  5. Skipping tests for edge cases - Empty arrays, null values, and error responses are common in production. Test these scenarios explicitly.

Practice Questions

  1. What class does aurelia-testing provide for rendering components in tests?
  2. How do you mock an HttpClient dependency for service tests?
  3. Why must you call component.dispose() after each component test?
  4. What is the purpose of the Bootstrap module in component tests?
  5. How do you test that a route navigates with the correct parameters?

Challenge: Write a complete test suite for a TodoList component. Include unit tests for the ViewModel (add, toggle, delete, clearCompleted), component tests for rendering, and integration tests that verify the full add-and-display workflow.

FAQ

What test runners does Aurelia support?

Aurelia works well with Karma, Jest, and Web Test Runner. The official documentation examples use Karma with Jasmine.

Can I test Aurelia apps without a browser?

For unit tests, you can use Node.js with jsdom. Component tests that render HTML require a browser environment like PhantomJS, Headless Chrome, or jsdom with DOM APIs.

How do I test value converters?

Value converters are simple classes with toView and fromView methods. Instantiate them directly and call the methods without any Aurelia infrastructure.

What is the StageComponent and how does it work?

StageComponent creates a detached DOM fragment, compiles your component with Aurelia's template compiler, and binds it to the specified view model. It simulates the framework's rendering pipeline without launching a full application.

How do I test custom attributes?

Use StageComponent with a template that applies the attribute. Test that the attribute modifies the element's properties, classes, or behavior as expected.

Mini Project

Write a test suite for a movie search application. Include unit tests for the search service (mock HTTP), component tests for the search view (renders results, shows loading, handles empty state), and integration tests for the route that navigates to a movie detail page.

What's Next

Put everything together in a complete Aurelia project that combines components, routing, HTTP, validation, and testing into a real-world application.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro