Wednesday, November 25, 2015

Testing custom elements

I know I promised my config file, but that will have to wait for this post...

I have a lot of custom elements in my app. Writing unit tests for them is one of the most important ways that I can have confidence that the app actually works. But I have killed myself the last 2 days trying to get the right incantation. I looked through the Aurelia unit tests, but since I haven't actually pulled them all locally, searching is hit-and-miss and I missed. Patrick Walters (patrickwalters.net) sent me a link to the test I was looking for, and I have been able to begin writing these tests. I thought I would share the boilerplate code I use in these tests.

import {TheCustomElement} from '../../src/components/the-custom-element';
import {Container} from 'aurelia-dependency-injection';
import {TemplatingEngine} from 'aurelia-templating';
import {initialize} from 'aurelia-pal-browser';
import {DOM} from 'aurelia-pal';
// Only need this (and the register below) if you need an Element in your component
let element = document.createElement('div');

describe('The element', () => {
    let sut;
    let container;
    let templatingEngine;

    beforeEach(function() {
        initialize();
        container = new Container();
        container.registerInstance(DOM.Element, element);
        templatingEngine = container.get(TemplatingEngine);
        sut = templatingEngine.createViewModelForUnitTest(TheCustomElement);
   });
});

This code leverages the testing function createViewModelForUnitTest to actually create the element to be tested. This in turns leverages the fact that the Aurelia DI system will auto register anything you need when you create something.

All in all, it's really simple.

Once you know the magic.

Update 12/11/2015. There's a little more to the process if you have mocks that you want to use. Since createViewModelForUnitTest will use the container to get your view model's dependencies, it will not by default use your mocks. However, you can control that by giving them to the DI container to use. Since the Aurelia DI container is a "first registration wins" container, this means you will want to register your mocks before everything else (granted, in this case there aren't many other options).

For example, let's assume that I have a service.js that interacts with my backend, and I have a mock that I want to use in my tests. To use the mock, I would need to change the above to:

import {TheCustomElement} from '../../src/components/the-custom-element';
import {Container} from 'aurelia-dependency-injection';
import {TemplatingEngine} from 'aurelia-templating';
import {initialize} from 'aurelia-pal-browser';
import {DOM} from 'aurelia-pal';

class MockServiceClass {   <== This is the same name as your real service class, so the DI system can line it up
    // mock code here
}
let element = document.createElement('div');

describe('The element', () => {
    let sut;
    let container;
    let templatingEngine;

    beforeEach(function() {
        initialize();
        container = new Container();
        container.registerInstance(DOM.Element, element);
        container.registerInstance(MyServiceClass, new MockServiceClass);  <== And use the mock here
        sut = templatingEngine.createViewModelForUnitTest(TheCustomElement);
   });
});

With that, the unit test will use my mock service, while creating a "real" element for testing.

Note, there is a second method in the templating engine: createControllerForUnitTest, that creates a Controller instance. In fact, createViewModelForUnitTest uses this method to do its work, simply returning the viewModel from the controller.

No comments: