StagedComponent Plugin Tests

Have you built an Aurelia plugin yet? If not it's a great way to share functionality within a generic way with other developers, plus it's fun as you learn tons of cool internals and best practices on writing safe Aurelia code.

The actual API is quite straightforward for building your plugin, but when it comes to testing things are a bit unclear. If you keep the focus on isolated unit tests, that means you don't need your environment staged under test, all is great. Otherwise, this article is going to explain how you can stage your plugin itself, in order to test your functionality via Aurelia-Testing helpers.

Why Staged Tests at all?

For quite a while it is sufficient to go with isolated unit tests. You're able to test all your APIs and get a good basic coverage set up. That should make up your basis and often solves 50%-70%. But let's say your plugin provides a custom attribute with additional features. Testing the backing code is done with isolated tests, but having the interaction of the custom attribute with the DOM and Aurelia's binding engine covered will require switching to integration tests.

And that is exactly what the Aurelia-Testing helpers like the StagedComponent help you to do.

Case study: Aurelia I18N Plugin

Instead of continuing to talk about a hypothetical plugin, lets instead see how all of this is done with the official Aurelia I18N Plugin. This plugin uses Karma as the test-runner and Jasmine as its assertion-library.

One of its features is the custom attribute called T – for a detailed description look here for the subsection Translating via html attributes – which simplifies declarative i18n assignments on a target element.

There is a spec file t-attribute.spec.js which depicts our scenario.

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

import { bootstrapTestEnvironment } from './staging-helpers';

describe('t-attribute', () => {  
  it('should convert bound integers to strings', done => {
    const target = 'test-target';
    const expectedValue = 'Foobar';
    const component = StageComponent
      .withResources('test/unit/mocks/rt-vm')
      .inView(`<div t.bind="integer" id=${target}></div>`)
      .boundTo({ integer: 1 });

    bootstrapTestEnvironment(component, { en:
      { translation: {
        '1': expectedValue
      }}
    });

    component.create(bootstrap)
      .then(() => {
        const elem = document.getElementById(target);
        expect(elem.innerHTML).toBe(expectedValue);

        done();
      });
  });
});

Creating a StagedComponent

The target, as well as the expectedValue, are specific to the given test, as such let's skip right away to the component instantiation, where a mocked ViewModel is referenced which is nothing more than this:

// test/unit/mocks/rt-vm.js
export class RtVm {}  

A companion rt-vm.html is thus automatically loaded by the StagedComponent, which is located right next to the ViewModel and also just contains the bare minimum:

<template></template>  

The chained inView method is now where we actually setup up our test, in the specific case we're testing that an integer may be bound to the attribute. The additional id is just a helper to later on easily retrieve the rendered DOM element.
Last but not least boundTo provides the effective data for our ViewModel.

Although our ViewModel had no definition of a property integer, boundTo will happily create one and assign the provided value.

Using the Plugin in the Test-Environment

Now in order to connect the plugin's sources lets take a look at the helper function bootstrapTestEnvironment.

export function bootstrapTestEnvironment(component, resources) {  
  component.bootstrap((aurelia) => {
    aurelia.use
      .standardConfiguration()
      .feature('src', () => {
        // Your plugins setup method
      });
  });
}

The important part is that we treat the plugin as a feature and call to the src folder of our plugin. This works perfectly with one drawback. Starting your Karma tests, you will see that a request is made for src/index.js. In case of the I18N plugin, this is a problem since the initial file is called aurelia-i18n.js following a convention from other Aurelia repositories.

In order to work around this issue, we finally need to touch the Karma configuration file.
With the help of the proxies property, we can define that a request for the index file should be re-routed to the actual file.

proxies: {  
 ...
 // redirect aurelia.feature's request of the index file to the actual setup
 '/base/src/index.js': '/base/src/aurelia-i18n.js'
},

and additionally we're also providing the mocked VM as part of our test infrastructure:

jspm: {  
  serveFiles: [..., 'test/unit/mocks/**/*-vm.*']
},

Conclusion

With all that setup we are now able to test our plugin itself staged for our test environment, by leveraging the trick of treating the plugin as a feature.
Now there are no more excuses for not writing proper unit tests to cover all the weird use cases ... damn ;)

photo credit: Desertrose7: Stage Performance via Pixabay (license)