Skip to content


Unit testing Aurelia service code

Aurelia is one of a crop of new front end JavaScript frameworks that make it easier to manage complex interactions in the browser. It implements a MVVM pattern and includes routing, dependency injection etc.

Unit testing Aurelia custom elements and attributes is described in the Aurelia docs (http://aurelia.io/hub.html#/doc/article/aurelia/testing/latest/testing-components. However, more general testing of business logic or service code is not discussed. This article gives a basic introduction to testing these classes using the Aurelia CLI.

  1. What libraries are included in the CLI, what do they do?
  2. Writing a basic test
  3. Running tests
  4. Improving the test output
  5. Debugging tests
  6. Mocking in tests
  7. Testing code with promises.

Note: Aurelia supports the babel.js and Typescript transpilers. The code examples below are in Typescript but should be readable for anyone who is familiar with modern javascript.

1.      What libraries are included in the CLI, what do they do?

When creating an Aurelia project using the CLI the following testing libraries are included:

  • Jasmine – popular BDD JavaScript testing framework. Provides the test structure and asserts.
  • Karma – test runner. Allows you to run the tests from the command line and debug the tests within a browser.

Angular Protractor and selenium web-driver tests are also included – they are used for end to end testing and so not discussed in this article.

2.      Writing a basic test

Test classes are placed in the /tests folder and should include spec in the title e.g. articleStore.spec.js. The test class should include the following elements.

Import referenced classes

Import the class under test (and any other relevant classes) at the top of the test class e.g.

Import { ArticleStore } from ‘../../src/articles/ArticleStore’;

Create a top level describe function.

Create a describe function that indicates the name of the class under test as a string and the details of the tests as an argument e.g.

describe(‘the ArticleStore’, () => {…});

Note: The text ‘the ArticleStore’ will then be outputted when we run the tests.

Create a test setup

As with most testing framework Jasmine provides a mechanism to run setup and teardown code before/after each individual test. This is achieved by creating a beforeEach/afterEach function that takes a function as an argument.

In this example we will use this to create an instance of the class under test before each test is run e.g.

let target: ArticleStore;

beforeEach(() => {

      this.target = new ArticleStore();

});

Create a test

To create the test itself we create an it() function which takes the name of the test as a string and the test code as an function argument. Again, the name of the test will be outputted by the test runner.

The test itself makes use of the Jasmine asserts to confirm expected state.

e.g.

it(“should have an empty articles collection”, () = {

      expect(this.target.Articles).not.toBeNull();

      expect(this.target.Articles.length).toBe(0);

});

3.      Running tests

To run the tests issue the following statement from a command prompt:

au test

This invokes the Karma test runner to run any tests it finds in files ending spec.js and outputs the details of any tests that have failed.

“au test” will run the tests once, report the output and close. However, as with the “Aurelia run” command you can include the watch argument:

au test --watch

This will run the tests, report the output but not close. The test runner will maintain a watch for any changes to code and when new code is saved will rerun the tests and display the new output.

4.      Improving the test output

The default configuration of Karma within Aurelia will only report failing tests. To get a comprehensive list of all tests that were run make the following change to the /karma.conf.js file:

Change this:      reporters: [‘progress’],

To this:               reporters: [‘spec’],

5.      Debugging tests

Karma makes debugging quite easy as it creates a browser instance and allows you to use the standard in browser debug tools (F12). As well as being simple to use, debugging in the browser is more accurate than debugging in an IDE or similar, as it will correctly replicate any browser issues.

To debug from the Karma test runner:

  • In the command prompt run: au test –watch
  • A browser spins up. Click the ‘Debug’ button in the green bar on the top right.
  • A new tab opens with the unit tests loaded. Press F12 to open developer tools, view the source, add breakpoints etc. as normal and press refresh to re-run the tests and hit the breakpoints.

6.      Mocking in Aurelia tests

Mocking is an important part of any unit testing strategy. Currently the standard tool for mocks/stubs/spies in Javascipt is to use the sinon.js library. However in my experience this did not play well with Jasmine.

An alternative, simpler and more modern mocking library is called TestDouble (https://github.com/testdouble/testdouble.js) . This can be imported via NPM.

Import the testdouble package

npm install testdouble –save-dev

Add testdouble as a vendor-bundle dependency.

This is optional but adding the following to the dependencies section of the /aurelia-project/aurelia.json file, vendor-bundle dependencies section makes regularly including the testdouble library within test classes simpler as you can refer to it with a simple name rather than needing the relative path.

{
        "name": "testdouble",
        "path": "../node_modules/testdouble/dist/testdouble"
}

 

Import TestDouble in your test class

Import * as TestDouble from ‘testdouble’;

Note include the relative path if you did not complete the previous step

 

Create/Destroy the mock objects

Within the test code create a variable for the object being mocked, with the type of the object being mocked e.g. if we are mocking a class of type ApiConnector

let apiConnector: ApiConnector;

Initialize the mock on the beforeEach() method, and call TestDouble.reset() on the afterEach() method e.g.

beforeEach(() => {
      this.apiConnector = TestDouble.object(ApiConnector);
});
afterEach(() => {
      TestDouble.reset();
});

 

Setup and/or verify the mocks within the tests

It(‘getStatus should return the updating status from the apiConnector”, () => {

      // setup the mock
      let knownStatus = “cached”;
      TestDouble.when(this.apiConnector.getStatus).thenReturn(knownStatus);

      // make the call under test
      Let result = this.articleStore.getStatus();
      
      // assert the mocked result is returned and the call was made on the mock object
      Expect(result).toBe(knownStatus);

      this.articleStore.verify(getApiStatus);
});

 

7.      Testing code containing promises

Asynchronous code is very common in the JavaScript world and a modern approach to implementing async code is to use promises. Asynchronous code and promises requires some minor changes in testing approach.

To handle async code Jasmine takes an optional argument to the it(), beforeEach() and afterEach() methods called “done”. The “done” argument is a method that should be called once all other test code has completed. When using a promises approach to asynchronous code this would typically be at the end of the last “then” call.

The following code gives an example of creating a promise object and returning this promise from a mocked method. This is a common scenario e.g. where an ajax call which returns a promise needs to be mocked.

For a promise to be processed either its resolve or reject method should be called. In this example the mock returns a promise that has a resolve method and the test asserts that when that promise is successfully resolved the refreshAll method returns a promise with the data true.

 

it("refreshAll returns a promise with data:true when api call successful", (done) =>  {
       // arrange       
        let promise = new Promise((resolve, reject) => { resolve("Success data"); });

        TestDouble.when(this.apiConnector.getMany()).thenReturn(promise);

        // act
        let result = this.articleStore.refreshAll();

        // assert
        result.then(data => {
            expect(data).toBe(true);

            done();
        }); 

    });

 

Complete example code

As a summary, I have included below a complete example of an Aurelia test class which mocks promises:

import { ArticleStore } from '../../../../src/resources/data-service/ArticleStore';

import { ApiConnector } from '../../../../src/resources/data-service/ApiConnector';

import * as TestDouble from 'testdouble';

describe('the ArticleStore', () => {

    // setup

    let apiConnector: ApiConnector;

    let articleStore: ArticleStore;

    beforeEach(() => {

        TestDouble.reset();
        this.apiConnector = TestDouble.object(ApiConnector);
        this.articleStore = new ArticleStore(this.apiConnector);       

    });




    afterEach(() => {
        TestDouble.reset();
    });




    it("refreshAll returns a promise with data:true when api call successful", (done) =>  {
        // arrange       
        let promise = new Promise((resolve, reject) => { resolve("Success data"); });
        TestDouble.when(this.apiConnector.getMany()).thenReturn(promise);

        // act
        let result = this.articleStore.refreshAll();

        // assert
        result.then(data => {
            expect(data).toBe(true);
            done();
        }); 

    });

});

Posted in Uncategorized.


0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.



Some HTML is OK

or, reply to this post via trackback.