
Testing UI with EmberJS
Martin Nuc
@martin_nuc
Topics
- why Ember?
- tests in Ember
- page objects
- Ember goodies
- what are we up to next?
Why Ember?
Test pyramid

Acceptance
Integration
Unit
moduleFor('service:discussion', 'Unit | Service | discussion', {
needs: ['service:ajax']
});
test('it deletes question', function(assert) {
assert.expect(3);
let service = this.subject({
record: EmberObject.create({ id: '1' }),
ajax: EmberObject.create({
put(url, { data }) {
assert.equal(url, 'loans/1/questions/2');
assert.deepEqual(data, { deleted: true });
return resolve();
},
buildURL(url) {
return url;
}
}),
refetchQuestions() {
assert.ok(true);
return resolve();
}
});
let question = EmberObject.create({ id: '2' });
return service.deleteQuestion({ question });
});
Integration
test('it renders question with answer', function(assert) {
this.set('question', {
message: 'How are you?',
isActive: true,
isAnswered: true,
answer: {
isActive: true,
message: 'Donuts'
}
});
component.render(hbs`
{{marketplace-discussion-question question=question}}
`);
assert.equal(component.message.text.trim(), 'How are you?');
assert.equal(component.answer.message.text.trim(), '- Donuts');
});
Acceptance
test('Marge can delete her question at discussion on marketplace detail', async assert => {
await login.visit().authenticate({ email: 'marge@zonkej.cz' });
await detail.visit({ purpose: 'auto-moto', slug: 'marketplace-#1-1' });
assert.equal(detail.discussion.status.show.text.trim(), 'Dotazy (10)');
await detail.discussion.status.show.click();
await detail.discussion.panel.deleteQuestionAt(1);
assert.equal(
detail.discussion.panel.questions(1).message.text.trim(),
'Lorem ipsum...'
);
await detail.discussion.status.hide.click();
assert.equal(detail.discussion.status.show.text.trim(), 'Dotazy (9)');
});

QUnit
Page object
Page object
import detail from 'zonky-app/tests/pages/marketplace/primary/detail';
...
assert.equal(detail.discussion.status.show.text.trim(), 'Dotazy (10)');
zonky-app/tests/pages/marketplace/primary/detail:
import detail from 'zonky-app/tests/pages/components/marketplace-detail';
export default create(
Object.assign({}, detail, {
visit: visitable('/:purpose/:slug')
})
);
Page object tree

zonky-app/tests/pages/components/marketplace-detail.js:
import header from 'zonky-app/tests/pages/components/application-header';
import navigation from
'zonky-app/tests/pages/components/marketplace-detail-navigation';
...
export default {
header,
index: {
scope: '[data-test-marketplace-detail="index"]'
},
category: {
scope: '[data-test-marketplace-detail="category"]'
},
navigation,
...
}
Mirage

Mirage runs in browser!
- everything one on page refresh
- possible to run in every browser (inc. mobile)
- for both running app and automated tests
- doesn't wait for backend
Mirage
mirage.get('/loans/:id/investments', ({ db }, request) => {
const recordId = request.params.id;
return A(db.marketplaceInvestments.where({ recordId })).map(item => {
delete item.recordId;
return item;
});
});
Browserstack
Browserstack
- easy thanks to plugins
- avoid unsupported functions

What's next?
Percy.io
Image diff

Visual regression testing
- pixel perfect
- covers visual changes
- part of code review

Thank you
Questions?
UI testing in EmberJS
By Martin Nuc
UI testing in EmberJS
- 391