LogoLogo
HomeDiscourseBlogDiscord
  • Introduction
  • Introduction
    • Quick start
    • Aurelia for new developers
    • Hello world
      • Creating your first app
      • Your first component - part 1: the view model
      • Your first component - part 2: the view
      • Running our app
      • Next steps
  • Templates
    • Template Syntax
      • Attribute binding
      • Event binding
      • Text interpolation
      • Template promises
      • Template references
      • Template variables
      • Globals
    • Custom attributes
    • Value converters (pipes)
    • Binding behaviors
    • Form Inputs
    • CSS classes and styling
    • Conditional Rendering
    • List Rendering
    • Lambda Expressions
    • Local templates (inline templates)
    • SVG
  • Components
    • Component basics
    • Component lifecycles
    • Bindable properties
    • Styling components
    • Slotted content
    • Scope and context
    • CustomElement API
    • Template compilation
      • processContent
      • Extending templating syntax
      • Modifying template parsing with AttributePattern
      • Extending binding language
      • Using the template compiler
      • Attribute mapping
  • Getting to know Aurelia
    • Routing
      • @aurelia/router
        • Getting Started
        • Creating Routes
        • Routing Lifecycle
        • Viewports
        • Navigating
        • Route hooks
        • Router animation
        • Route Events
        • Router Tutorial
        • Router Recipes
      • @aurelia/router-lite
        • Getting started
        • Router configuration
        • Configuring routes
        • Viewports
        • Navigating
        • Lifecycle hooks
        • Router hooks
        • Router events
        • Navigation model
        • Transition plan
    • App configuration and startup
    • Enhance
    • Template controllers
    • Understanding synchronous binding
    • Dynamic composition
    • Portalling elements
    • Observation
      • Observing property changes with @observable
      • Effect observation
      • HTML observation
      • Using observerLocator
    • Watching data
    • Dependency injection (DI)
    • App Tasks
    • Task Queue
    • Event Aggregator
  • Developer Guides
    • Animation
    • Testing
      • Overview
      • Testing attributes
      • Testing components
      • Testing value converters
      • Working with the fluent API
      • Stubs, mocks & spies
    • Logging
    • Building plugins
    • Web Components
    • UI virtualization
    • Errors
      • 0001 to 0023
      • 0088 to 0723
      • 0901 to 0908
    • Bundlers
    • Recipes
      • Apollo GraphQL integration
      • Auth0 integration
      • Containerizing Aurelia apps with Docker
      • Cordova/Phonegap integration
      • CSS-in-JS with Emotion
      • DOM style injection
      • Firebase integration
      • Markdown integration
      • Multi root
      • Progress Web Apps (PWA's)
      • Securing an app
      • SignalR integration
      • Strongly-typed templates
      • TailwindCSS integration
      • WebSockets Integration
      • Web Workers Integration
    • Playground
      • Binding & Templating
      • Custom Attributes
        • Binding to Element Size
      • Integration
        • Microsoft FAST
        • Ionic
    • Migrating to Aurelia 2
      • For plugin authors
      • Side-by-side comparison
    • Cheat Sheet
  • Aurelia Packages
    • Validation
      • Validation Tutorial
      • Plugin Configuration
      • Defining & Customizing Rules
      • Architecture
      • Tagging Rules
      • Model Based Validation
      • Validation Controller
      • Validate Binding Behavior
      • Displaying Errors
      • I18n Internationalization
      • Migration Guide & Breaking Changes
    • i18n Internationalization
    • Fetch Client
      • Overview
      • Setup and Configuration
      • Response types
      • Working with forms
      • Intercepting responses & requests
      • Advanced
    • Event Aggregator
    • State
    • Store
      • Configuration and Setup
      • Middleware
    • Dialog
  • Tutorials
    • Building a ChatGPT inspired app
    • Building a realtime cryptocurrency price tracker
    • Building a todo application
    • Building a weather application
    • Building a widget-based dashboard
    • React inside Aurelia
    • Svelte inside Aurelia
    • Synthetic view
    • Vue inside Aurelia
  • Community Contribution
    • Joining the community
    • Code of conduct
    • Contributor guide
    • Building and testing aurelia
    • Writing documentation
    • Translating documentation
Powered by GitBook
On this page
  • Understanding Mocks, Stubs, and Spies
  • Using Sinon for Mocking, Stubbing, and Spying
  • Installing Sinon
  • Using Sinon in Your Tests
  • Stubbing Individual Methods
  • Mocking an Entire Dependency
  • Spying on Methods
  • Mocking Dependencies Directly in the Constructor
  • Conclusion

Was this helpful?

Export as PDF
  1. Developer Guides
  2. Testing

Stubs, mocks & spies

Testing in Aurelia often involves testing components that have dependencies injected into them. Using dependency injection (DI) simplifies the process of replacing these dependencies with mocks, stubs, or spies during testing. This can be particularly useful when you need to isolate the component under test from external concerns like API calls or complex logic.

Understanding Mocks, Stubs, and Spies

  • Mocks are objects that replace real implementations with fake methods and properties that you define. They are useful for simulating complex behavior without relying on the actual implementation.

  • Stubs are like mocks but typically focus on replacing specific methods or properties rather than entire objects. They are useful when you want to control the behavior of a dependency for a particular test case.

  • Spies allow you to wrap existing methods so that you can record information about their calls, such as the number of times they were called or the arguments they received.

Using Sinon for Mocking, Stubbing, and Spying

Sinon is a popular library for creating mocks, stubs, and spies in JavaScript tests. It provides a rich API for controlling your test environment and can significantly simplify the process of testing components with dependencies.

Installing Sinon

To make use of Sinon in your Aurelia project, you need to install it along with its type definitions for TypeScript support:

npm install sinon @types/sinon -D

If you are not using TypeScript, you can omit the @types/sinon.

Using Sinon in Your Tests

After installing Sinon, import it in your test files to access its functionality. Let's look at how to apply Sinon to mock, stub, and spy on dependencies in Aurelia components.

my-component.ts
import { IRouter } from '@aurelia/router';
import { customElement, resolve } from 'aurelia';

@customElement('my-component')
export class MyComponent {
    constructor(private router: IRouter = resolve(IRouter)) {}

    navigate(path: string) {
        return this.router.load(path);
    }
}

In this example, the MyComponent class has a dependency on IRouter and a method navigate that delegates to the router's load method.

Stubbing Individual Methods

To stub the load method of the router, use Sinon's stub method:

import { createFixture } from '@aurelia/testing';
import { MyComponent } from './my-component';
import { IRouter } from '@aurelia/router';
import sinon from 'sinon';

describe('MyComponent', () => {
    it('should stub the load method of the router', async () => {
        const { startPromise, component, container, tearDown } = createFixture(
            `<my-component></my-component>`,
            MyComponent,
            []
        );

        await startPromise;

        const router = container.get(IRouter);
        const stub = sinon.stub(router, 'load').returnsArg(0);

        expect(component.navigate('nowhere')).toBe('nowhere');

        stub.restore();
        await tearDown();
    });
});

Mocking an Entire Dependency

When you need to replace the entire dependency, create a mock object and register it in place of the real one:

import { createFixture, Registration } from '@aurelia/testing';
import { MyComponent } from './my-component';
import { IRouter } from '@aurelia/router';

const mockRouter = {
    load(path: string) {
        return path;
    }
};

describe('MyComponent', () => {
    it('should use a mock router', async () => {
        const { startPromise, component, tearDown } = createFixture(
            `<my-component></my-component>`,
            MyComponent,
            [],
            [Registration.instance(IRouter, mockRouter)]
        );

        await startPromise;

        expect(component.navigate('nowhere')).toBe('nowhere');

        await tearDown();
    });
});

By using Registration.instance, we can ensure that any part of the application being tested will receive our mock implementation when asking for the IRouter dependency.

Spying on Methods

To observe and assert the behavior of methods, use Sinon's spies:

magic-button.ts
import { customElement } from 'aurelia';

@customElement('magic-button')
export class MagicButton {
    callbackFunction(event: Event, id: number) {
        return this.save(event, id);
    }

    save(event: Event, id: number) {
        // Pretend to call an API or perform some action...
        return `${id}__special`;
    }
}

To test that the save method is called correctly, wrap it with a spy:

magic-button.spec.ts
import { createFixture } from '@aurelia/testing';
import { MagicButton } from './magic-button';
import sinon from 'sinon';

describe('MagicButton', () => {
    it('calls save when callbackFunction is invoked', async () => {
        const { startPromise, component, tearDown } = createFixture(
            `<magic-button></magic-button>`,
            MagicButton
        );

        await startPromise;

        const spy = sinon.spy(component, 'save');
        component.callbackFunction(new Event('click'), 123);

        expect(spy.calledOnceWithExactly(new Event('click'), 123)).toBeTruthy();

        spy.restore();
        await tearDown();
    });
});

Mocking Dependencies Directly in the Constructor

Unit tests may require you to instantiate classes manually rather than using Aurelia's createFixture. In such cases, you can mock dependencies directly in the constructor:

my-component.spec.ts
import { MyComponent } from './my-component';
import { IRouter } from '@aurelia/router';

describe('MyComponent', () => {
    const mockRouter: IRouter = {
        load(path: string) {
            return path;
        }
        // ... other methods and properties
    };

    it('should navigate using the mock router', () => {
        const component = new MyComponent(mockRouter);

        expect(component.navigate('somewhere')).toBe('somewhere');
    });
});

In this test, we directly provide a mock router object when creating an instance of MyComponent. This technique is useful for more traditional unit testing where you want to test methods in isolation.

Conclusion

Mocking, stubbing, and spying are powerful techniques that can help you write more effective and isolated tests for your Aurelia components. By leveraging tools like Sinon and Aurelia's dependency injection system, you can create test environments that are both flexible and easy to control. Whether you're writing unit tests or integration tests, these methods will enable you to test your components' behavior accurately and with confidence.

PreviousWorking with the fluent APINextLogging

Last updated 1 year ago

Was this helpful?