Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
The Next Generation of Testing in Ember.js
Search
Tobias Bieniek
March 13, 2018
Technology
1k
3
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
The Next Generation of Testing in Ember.js
Tobias Bieniek
March 13, 2018
More Decks by Tobias Bieniek
See All by Tobias Bieniek
Please wait… Oh, it didn't work!
turbo87
0
240
Abstract Syntax Forestry
turbo87
0
73
Help! How do you let others know what’s going on when you’re 8000 feet up in the air in a plane without an engine?!
turbo87
0
140
ELS – The Ember Language Server
turbo87
0
270
Internationali[sz]ation: It's easy in Ember!
turbo87
1
430
EuregioCup 2018 - Selbstbriefing
turbo87
1
77
750 km FAI-Dreieck mit der Clubklasse
turbo87
3
81
High Level DOM Assertions for QUnit
turbo87
0
140
EuregioCup 2017 - Selbstbriefing
turbo87
1
61
Other Decks in Technology
See All in Technology
product engineering with qa
nealle
0
140
フルカイテン株式会社 エンジニア向け採用資料
fullkaiten
0
11k
Claude Codeとハーネスについて考えてみる
oikon48
10
3.8k
そこにあるから地図ができる~位置を示す"モノ"を愉しむ~ - Interface 2026年6月号GPS特集オフ会 / interface_202606_GPS_offline
sakaik
1
190
SRE歴2ヶ月でも開発6年の知見を活かして、チームで止まっていた環境改善を前に進めた話
a_ono
0
170
AIDLC_ヤフーショッピングの取り組み
lycorptech_jp
PRO
0
500
AI時代における最適なQA組織の作り方
ymty
3
340
小さいから、全部わかる。— 常駐AI "xangi" のすすめ
sugupoko
0
240
クラウドファンディング版StackChan 3体(4体)をインタラクティブな体験型作品にして展示もした話 / スタックチャンお誕生日会2026
you
PRO
0
270
From Prompt Engineering to Loop Engineering
shibuiwilliam
1
330
【FinOps】データドリブンな意思決定を目指して
z63d
3
590
Baseline対応のDOMの型定義を作った
uhyo
2
610
Featured
See All Featured
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.1k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.5k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
118
120k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
2
880
[RailsConf 2023] Rails as a piece of cake
palkan
59
6.7k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
72
40k
How to build a perfect <img>
jonoalderson
1
5.8k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
330
The Illustrated Children's Guide to Kubernetes
chrisshort
51
52k
Typedesign – Prime Four
hannesfritz
42
3.1k
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.3k
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
1.9k
Transcript
The Next Generation of Testing in __
Turbo87 TobiasBieniek
simplabs based in Munich consulting all over ! * Stickers
available after the talk
Ember CLI
⏰ andThen() The andThen helper will wait for all preceding
asynchronous helpers to complete prior to progressing forward.
andThen() import { test } from 'qunit'; import moduleForAcceptance from
'my-app/tests/helpers/module-for-acceptance'; moduleForAcceptance('Acceptance | posts'); test('should add new post', function(assert) { visit('/posts/new'); fillIn('input.title', 'My new post'); click('button.submit'); andThen(() => assert.equal(find('ul.posts li:first').text(), 'My new post')); });
andThen() import { test } from 'qunit'; import moduleForAcceptance from
'my-app/tests/helpers/module-for-acceptance'; moduleForAcceptance('Acceptance | posts'); test('should add new post', function(assert) { return visit('/posts/new').then(function() { return fillIn('input.title', 'My new post'); }).then(function() { return click('button.submit'); }).then(function() { assert.equal(find('ul.posts li:first').text(), 'My new post') }); }); April 2013
andThen() vs. async /await import { test } from 'qunit';
import moduleForAcceptance from 'my-app/tests/helpers/module-for-acceptance'; moduleForAcceptance('Acceptance | posts'); test('should add new post', async function(assert) { await visit('/posts/new'); await fillIn('input.title', 'My new post'); await click('button.submit'); assert.equal(find('ul.posts li:first').text(), 'My new post'); });andThen
andThen() vs. async /await andThen() • almost 5 years old
• mixes sync and async code • unintuitive API async / await • standardized ECMAScript • explicit async code • Promise-based
None
30 kB no longer needed by Ember.js itself
30 kB still needed by the test helpers
jQuery await click('button.submit'); assert.equal(find('ul.posts li').text(), 'My new post'); this.$('button.submit').click(); assert.equal(this.$('ul.posts
li').text(), 'My new post'); jQuery jQuery jQuery Acceptance Component / Integration
jQuery await click('button.submit'); assert.equal(find('ul.posts li').text(), 'My new post');
jQuery import { click, find } from 'ember-native-dom-helpers'; await click('button.submit');
assert.equal(find('ul.posts li').textContent, 'My new post');
jQuery import { click, find } from '@ember/test-helpers'; await click('button.submit');
assert.equal(find('ul.posts li').textContent, 'My new post');
Nested Modules
Nested Modules describe('Galaxy', function() { describe('Earth', function() { describe('Portland', function()
{ it('is awesome at EmberConf', function() { // right? });aaa }); }); });
Nested Modules describe('Galaxy', function() {hooks describe('Earth', function() {hooks beforeEach(function() {hooks
this.owner.lookup('service:time').setYear(2018); }); describe('Portland', function() {hooks it('is awesome at EmberConf', function() {assert // right? });aaa }); }); });
Nested Modules module('Galaxy', function() { module('Earth', function(hooks) { hooks.beforeEach(function() {
this.owner.lookup('service:time').setYear(2018); }); module('Portland', function() { test('is awesome at EmberConf', function(assert) { // right? });aaa }); }); });
Nested Modules module('Galaxy', function(hooks) { module('Earth', function(hooks) { hooks.beforeEach(function() {
this.owner.lookup('service:time').setYear(2018); }); module('Portland', function(hooks) { test('is awesome at EmberConf', function(assert) { // right? });aaa }); }); }); Not compatible with • moduleFor() • moduleForComponent() • moduleForModel() • moduleForAcceptance()
The Grand Testing Unification
RFC #119
RFC #119
RFC #232
RFC #232 Replaces • moduleFor() • moduleForComponent() • moduleForModel() with
• module() + setupTest()
RFC #268
RFC #268 Replaces • moduleForAcceptance() with • module() + setupApplicationTest()
Plain QUnit tests for code unrelated to Ember
Plain QUnit tests import { module, test } from 'qunit';
module('relativeDate', function(hooks) { test('format relative dates correctly', function(assert) { assert.equal(relativeDate('2018/01/28 22:24:30'), 'just now'); assert.equal(relativeDate('2018/01/28 22:23:30'), '1 minute ago'); assert.equal(relativeDate('2018/01/28 21:23:30'), '1 hour ago'); assert.equal(relativeDate('2018/01/27 22:23:30'), 'Yesterday'); assert.equal(relativeDate('2018/01/26 22:23:30'), '2 days ago'); }); }); regular QUnit APIs
"Container" tests for Controllers, Routes, Services, ... (previously Unit /
Integration)
import { module, test } from 'qunit'; import { setupTest
} from 'ember-qunit'; module('Service | flash-messages', function(hooks) { setupTest(hooks); test('it buffers messages', function(assert) { let service = this.owner.lookup('service:flash-messages'); service.add('Hello'); service.add('World!'); assert.deepEqual(service.get('messages'), ['Hello', 'World!']); }); }); setupTest() regular QUnit APIs ember-qunit APIs
import { module, test } from 'qunit'; import { setupTest
} from 'ember-qunit'; module('Service | flash-messages', function(hooks) { setupTest(hooks); test('it buffers messages', function(assert) { let service = this.owner.lookup('service:flash-messages'); service.add('Hello'); service.add('World!'); assert.deepEqual(service.get('messages'), ['Hello', 'World!']); }); }); setupTest() Sets up the Testing Container
import { module, test } from 'qunit'; import { setupTest
} from 'ember-qunit'; module('Service | flash-messages', function(hooks) { setupTest(hooks); test('it buffers messages', function(assert) { let service = this.owner.lookup('service:flash-messages'); service.add('Hello'); service.add('World!'); assert.deepEqual(service.get('messages'), ['Hello', 'World!']); }); }); setupTest() Direct Container Access
import { module, test } from 'qunit'; import { setupTest
} from 'ember-qunit'; module('Service | flash-messages', function(hooks) { setupTest(hooks); test('it buffers messages', function(assert) { let service = this.owner.lookup('service:flash-messages'); service.add('Hello'); service.add('World!'); assert.deepEqual(service.get('messages'), ['Hello', 'World!']); }); }); setupTest()
import { module, test } from 'qunit'; import { setupTest
} from 'ember-qunit'; module('Service | flash-messages', function(hooks) { setupTest(hooks); test('it buffers messages', function(assert) { let service = this.owner.lookup('service:flash-messages'); service.add('Hello'); service.add('World!'); assert.deepEqual(service.get('messages'), ['Hello', 'World!']); }); }); setupTest()
Rendering tests for Components and Helpers (previously Component-Integration)
import { module, test } from 'qunit'; import { setupRenderingTest
} from 'ember-qunit'; import { render, click } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('Component | counter', function(hooks) { setupRenderingTest(hooks); setupRenderingTest() Shared between QUnit and Mocha
module('Component | counter', function(hooks) { setupRenderingTest(hooks); test('it should count clicks',
async function(assert) { this.set('value', 0); await render(hbs`{{x-counter value=value onUpdate=( ...)}}`); assert.equal(this.element.textContent, '0 clicks'); await click('.counter'); assert.equal(this.element.textContent, '1 click'); }); }); setupRenderingTest() similar to setupTest()
module('Component | counter', function(hooks) { setupRenderingTest(hooks); test('it should count clicks',
async function(assert) { this.set('value', 0); await render(hbs`{{x-counter value=value onUpdate=( ...)}}`); assert.equal(this.element.textContent, '0 clicks'); await click('.counter'); assert.equal(this.element.textContent, '1 click'); }); }); setupRenderingTest()
Application tests to verify user stories (previously Acceptance)
import { module, test } from 'qunit'; import { setupApplicationTest
} from 'ember-qunit'; import { visit, fillIn, click } from '@ember/test-helpers'; module('Acceptance | posts', function(hooks) { setupApplicationTest(hooks); test('should add new post', async function(assert) { await visit('/posts/new'); await fillIn('input.title', 'My new post'); await click('button.submit'); let title = this.element.querySelector('ul.posts li:first').textContent; assert.equal(title, 'My new post'); }); }); setupApplicationTest() same helpers as for rendering tests
import { module, test } from 'qunit'; import { setupApplicationTest
} from 'ember-qunit'; import { visit, fillIn, click } from '@ember/test-helpers'; module('Acceptance | posts', function(hooks) { setupApplicationTest(hooks); test('should add new post', async function(assert) { await visit('/posts/new'); await fillIn('input.title', 'My new post'); await click('button.submit'); let title = this.element.querySelector('ul.posts li:first').textContent; assert.equal(title, 'My new post'); }); }); setupApplicationTest() similar to setupRenderingTest()
import { module, test } from 'qunit'; import { setupApplicationTest
} from 'ember-qunit'; import { visit, fillIn, click } from '@ember/test-helpers'; module('Acceptance | posts', function(hooks) { setupApplicationTest(hooks); test('should add new post', async function(assert) { await visit('/posts/new'); await fillIn('input.title', 'My new post'); await click('button.submit'); let title = this.element.querySelector('ul.posts li:first').textContent; assert.equal(title, 'My new post'); }); }); setupApplicationTest()
ember install ???
ember install ember-cli-qunit 4.2.0+
Migration What about the 5000 tests we already have?
Migration async/await + ember-native-dom-helpers , ember-native-dom-helpers-codemod
ember-qunit-codemod Migration setupTest() functions .
ember-test-helpers-codemod Migration @ember/test-helpers /
Tips & Tricks ✨ ✨
Mocking aka. taking advantage of inject()
Mocking test('initializes with the cookie value', function() { this.owner.register('service:cookies', Service.extend({
read(key) { return 'a;b;c'; }, })); let featuresService = this.owner.lookup('service:features'); assert.deepEqual(featuresService.get('features'), ['a', 'b', 'c']); }); Register Mock Object
Mocking test('initializes with the cookie value', function() { this.owner.register('service:cookies', Service.extend({
read(key) { return 'a;b;c'; } })); let featuresService = this.owner.lookup('service:features'); assert.deepEqual(featuresService.get('features'), ['a', 'b', 'c']); }); Lookup Object using the Mock
Mocking test('initializes with the cookie value', function() { this.owner.register('service:cookies', Service.extend({
read(key) { return 'a;b;c'; } })); let featuresService = this.owner.lookup('service:features'); assert.deepEqual(featuresService.get('features'), ['a', 'b', 'c']); });
Mocking test('initializes with the cookie value', function() { this.owner.register('service:cookies', Service.extend({
read(key) { return 'a;b;c'; } })); let featuresService = this.owner.lookup('service:features'); assert.deepEqual(featuresService.get('features'), ['a', 'b', 'c']); });
Loading States How can we test them?
Loading States test('shows loading spinner after submitting', async function() {
await visit('/comments/new'); await fillIn('input.comment', 'I ❤ Ember.js'); let promise = click('.submit'); await waitFor('.loading-spinner'); await promise; assert.ok(find('.you-for-submitting')); }); Save Promise for later
Loading States test('shows loading spinner after submitting', async function() {
await visit('/comments/new'); await fillIn('input.comment', 'I ❤ Ember.js'); let promise = click('.submit'); await waitFor('.loading-spinner'); await promise; assert.ok(find('.you-for-submitting')); }); "Wait for loading spinner"
Loading States test('shows loading spinner after submitting', async function() {
await visit('/comments/new'); await fillIn('input.comment', 'I ❤ Ember.js'); let promise = click('.submit'); await waitFor('.loading-spinner'); await promise; assert.ok(find('.you-for-submitting')); }); Cleanup and final assertions
Loading States await waitFor('.loading-spinner'); same as: await waitUntil(() => find('.loading-spinner'));
Custom Test Helpers aka. registerAsyncHelper()
Custom Test Helpers export default registerAsyncHelper('addContact', function(app, name) { fillIn('#name',
name); click('button.create'); }); export async function addContact(name) { await fillIn('#name', name); await click('button.create'); }
Test Selectors the thing you know from CSS that helps
you find elements in the DOM
Test Selectors <h1>{{title}} </h1> <input class="title-field"> await fillIn('.title-field', 'Hello World!');
let title = this.element.querySelector('h1').textContent; assert.equal(title, 'Hello World!'); Test Selectors
Test Selectors <h1 data-test-title>{{title}} </h1> <input data-test-title-field> await fillIn('[data-test-title-field]', 'Hello
World!'); let title = this.element.querySelector('[data-test-title]').textContent; assert.equal(title, 'Hello World!');
Test Selectors <h1 data-test-title>{{title}} </h1> <input data-test-title-field> await fillIn('[data-test-title-field]', 'Hello
World!'); let title = this.element.querySelector('[data-test-title]').textContent; assert.equal(title, 'Hello World!'); ember install ember-test-selectors https://github.com/simplabs/ember-test-selectors
Readable Assertions How to write code that is easy to
understand
Readable Assertions let title = this.element.querySelector('[data-test-title]').textContent; assert.equal(title.trim(), 'Hello World!');
Readable Assertions let title = this.element.querySelector('[data-test-title]').textContent; assert.equal(title.trim(), 'Hello World!'); assert.dom('[data-test-title]').hasText('Hello
World!');
ember install qunit-dom https://github.com/simplabs/qunit-dom assert.dom('h1').exists(); assert.dom('h1').hasClass('title'); assert.dom('h1').hasText('Welcome to Ember, John
Doe!'); assert.dom('input').isFocused(); assert.dom('input').hasValue(/.+ Doe/); assert.dom('input').hasAttribute('type', 'text');
the Ember testing ecosystem ember-exam ember-cli-eslint ember-cli-code-coverage ember-data-factory-guy ember-native-dom-helpers ember-mirage
ember-try ember-cli-template-lint
Thanks