Tester un service est assez simple. Il suffit de l'importer et de l'instancier en lui passant des mocks de dépendances. Par contre, pour les Custom Elements, c'est plus compliqué car il faut le faire passer à travers le moteur de Aurelia. Pour cela, nous avons donc le module aurelia-testing
, inclus par défaut dans un projet créé avec aurelia-cli
. Voyons comment cela fonctionne.
(Attention, si vous voulez aussi utiliser async/await dans vos tests, il vous faudra modifier tests/unit/setup.js
pour y importer regenerator-runtime
. Mais vous le savez normalement si vous utilisez déjà async/await dans votre app)
Découverte
Prenons un custom element tout simple :
src/my-element.js
export class MyElement {}
src/my-element.html
<template>
<p>Hello World</p>
</template>
Voici le test :
tests/my-element.spec.js
import {StageComponent} from 'aurelia-testing';
import {bootstrap} from 'aurelia-bootstrapper';
describe('MyElement', () => {
let component;
beforeEach(() => {
component = StageComponent
.withResources('my-element') // Path related to src
.inView('<my-element><my-element>');
});
afterEach(() => {
component.dispose();
});
it('should render template', async done => {
await component.create(bootstrap);
const element = document.querySelector('my-element');
expect(element.innerText.trim()).toBe('Hello World');
});
});
StageComponent
va donc nous servir à rendre le composant dans le navigateur après l'avoir configuré comme on en a besoin. Ici, notre composant est très simple et n'a pas de configuration particulière. Nous nous contentons de vérifier que l'élément dans le DOM contient bien le texte spécifié dans le template.
Maintenant, essayons avec des données dynamiques
Attributs
src/my-element.js
import {bindable} from 'aurelia-framework';
export class MyElement {
@bindable foo;
}
src/my-element.html
<template>
<p>${foo}</p>
</template>
tests/my-element.spec.js
import {StageComponent} from 'aurelia-testing';
import {bootstrap} from 'aurelia-bootstrapper';
describe('MyElement', () => {
let component;
beforeEach(() => {
component = StageComponent
.withResources('my-element') // Path related to src
.inView(`
<my-element
foo.bind="foo"><my-element>
`)
.boundTo({
foo: 'foo'
});
});
afterEach(() => {
component.dispose();
});
it('should render foo', async done => {
await component.create(bootstrap);
const element = document.querySelector('my-element');
expect(element.innerText.trim()).toBe('foo');
});
});
Si on veut pouvoir modifier la valeur des propriétés, il va falloir passer en mode manuel grâce à manuallyHandleLifecycle
et appeler explicitement chaque étape du cycle de vie du composant:
tests/my-element.spec.js
import {StageComponent} from 'aurelia-testing';
import {bootstrap} from 'aurelia-bootstrapper';
describe('MyElement', () => {
let component;
beforeEach(() => {
component = StageComponent
.withResources('my-element') // Path related to src
.inView(`
<my-element
foo.bind="foo"><my-element>
`)
.boundTo({
foo: 'foo'
});
});
afterEach(() => {
component.dispose();
});
it('should render foo and changes value', async done => {
await component.manuallyHandleLifecycle().create(bootstrap);
await component.bind();
await component.attached();
const element = document.querySelector('my-element');
expect(element.innerText.trim()).toBe('foo');
await component.bind({
foo: 'bar'
});
expect(element.innerText.trim()).toBe('bar');
});
});
On peut aussi lire des propriétés du viewModel ou appeler des méthodes qui lui sont attachées via component.viewModel
:
src/my-element.js
import {bindable} from 'aurelia-framework';
export class MyElement {
@bindable onSelect;
select(item) {
if (typeof this.onSelect === 'function') {
this.onSelect(item)
}
}
}
src/my-element.html
<template>
<ul>
<li
click.delegate="select(1)">1</li>
<li
click.delegate="select(2)">2</li>
<li
click.delegate="select(3)">3</li>
</ul>
</template>
tests/my-element.spec.js
import {StageComponent} from 'aurelia-testing';
import {bootstrap} from 'aurelia-bootstrapper';
describe('MyElement', () => {
let component;
beforeEach(() => {
component = StageComponent
.withResources('my-element') // Path related to src
.inView(`
<my-element
on-select.bind="select"><my-element>
`);
});
afterEach(() => {
component.dispose();
});
it('should select an item', async done => {
let expected;
component.boundTo({
select(e) {
expected = e;
}
})
await component.create(bootstrap);
component.viewModel.select(42);
expect(expected).toBe(42);
});
});
Mais dans ce cas là, on peut aussi vouloir tester si la fonction est correctement exécutée lors du click sur le bon élément. On va donc rédiger notre test ainsi :
tests/my-element.spec.js
import {StageComponent} from 'aurelia-testing';
import {bootstrap} from 'aurelia-bootstrapper';
describe('MyElement', () => {
let component;
beforeEach(() => {
component = StageComponent
.withResources('my-element') // Path related to src
.inView(`
<my-element
on-select.bind="select"><my-element>
`);
});
afterEach(() => {
component.dispose();
});
it('should select an item on click', async done => {
let expected;
component.boundTo({
select(e) {
expected = e;
}
})
await component.create(bootstrap);
const lis = component.element.querySelectorAll('li');
lis[0].click();
expect(expected).toBe(1);
lis[1].click();
expect(expected).toBe(2);
lis[2].click();
expect(expected).toBe(3);
});
});
Slot
On peut de la même façon vérifier que les slots sont correctement remplis :
src/my-element.js
export class MyElement {}
src/my-element.html
<template>
<header
class="header">
<slot
name="header"></slot>
</header>
<div
class="content">
<slot
name="content"></slot>
</div>
</template>
tests/my-element.spec.js
import {StageComponent} from 'aurelia-testing';
import {bootstrap} from 'aurelia-bootstrapper';
describe('MyElement', () => {
let component;
beforeEach(() => {
component = StageComponent
.withResources('my-element') // Path related to src
.inView(`
<my-element>
<h1
slot="header">Title</h1>
<p
slot="content">Hello World"</p>
<my-element>
`);
});
afterEach(() => {
component.dispose();
});
it('should render title and content slots', async done => {
await component.create(bootstrap);
const element = document.querySelector('my-element');
expect(element.querySelector('.header').innerText.trim()).toBe('Title');
expect(element.querySelector('.content').innerText.trim()).toBe('Hello World');
});
});
Configuration
Il est parfois nécessaire de configurer son environnement différemment pour un test donné. Pour instancier un plugin nécessaire au fonctionnement du custom element, ou pour enregistrer un mock de dépendance dans l'injecteur. Nous allons gérer tout ça grâce à la méthode configure
de StageComponent
:
tests/my-element.spec.js
import {StageComponent} from 'aurelia-testing';
import {bootstrap} from 'aurelia-bootstrapper';
describe('MyElement', () => {
let component;
beforeEach(() => {
component = StageComponent
.withResources('my-element') // Path related to src
.inView(`
<my-element
on-select.bind="select"><my-element>
`);
component.configure = aurelia => {
aurelia.use
.standardConfiguration()
.plugin('aurelia-property-injection');
};
});
});
Ici, notre custom element fait usage de la lib aurelia-property-injection
et il faut donc le charger au préalable.
Si on a besoin de remplacer une dépendance par un mock, on peut faire ça aussi dans la configuration :
src/my-element.js
import {inject} from 'aurelia-framework';
import {MyService} from './my-service';
@inject(MyService)
export class MyElement {
constructor(myService) {
this.service = myService;
}
get something() {
return this.service.getSomething();
}
}
src/my-element.html
<template>
<p>${something}</p>
</template>
tests/my-element.spec.js
import {StageComponent} from 'aurelia-testing';
import {bootstrap} from 'aurelia-bootstrapper';
describe('MyElement', () => {
let component;
const MyServiceMocked = {
getSomething: jasmine.createSpy('getSomething').and.returnValue('something')
};
beforeEach(() => {
component = StageComponent
.withResources('my-element')
.inView(`
<my-element><my-element>
`);
component.configure = aurelia => {
aurelia.container.registerInstance('MyService', MyServiceMocked);
};
});
afterEach(() => {
component.dispose();
});
it('should render something', async done => {
await component.create(bootstrap);
expect(document.querySelector('p').innerText.trim()).toBe('something');
done();
});
});
Tout cela suffit déjà à tester la grande majorité des cas de figure. Nous verrons dans un prochain article comment teste les custom attributes.