Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
The Next Generation of Testing in Ember.js
Search
Tobias Bieniek
March 13, 2018
Technology
1.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
75
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
440
EuregioCup 2018 - Selbstbriefing
turbo87
1
78
750 km FAI-Dreieck mit der Clubklasse
turbo87
3
87
High Level DOM Assertions for QUnit
turbo87
0
140
EuregioCup 2017 - Selbstbriefing
turbo87
1
61
Other Decks in Technology
See All in Technology
~最速でビジネスインパクトを創出する~ Sansanのデータ基盤と活用事例
sansantech
PRO
0
110
AI × アーキテクト 第1・2章|生成AIの課題とアーキテクトの素養
daidaidai
1
220
Claude Code on Bedrock で統制をかける小ネタ集/claude-code-bedrock-governance-tips
emiki
1
140
Claude の新機能を、 出た日に触って社内に届けるまで
rfdnxbro
1
200
AIをリアル世界に解き放つ 〜M5Stack×AIであなたのアイディアを手の平に〜
hamadakoji
1
240
データエンジニアの次の戦場は "プロセスエンジニアリング"
sansantech
PRO
1
150
AI駆動開発におけるハーネスエンジニアリングの勘所
tsukuboshi
1
400
PHPカンファレンス関西の招待状 / Invitation to PHP Conference Kansai
ykanoh
0
220
DevelopersIO 2026 Tokyo 現時点の衛星通信サービスの現状を整理してみる
maroon1st
0
130
LLM-as-a-Judgeは本当に信頼できるのか――評価器を評価する話
taka_aki
2
1k
企画を Software Factory-ready にする——開発が高速になったとき、PdMの仕事はどう変わったか
yachibit
1
130
20年間 SNS サービス「GREE」 を支えた技術の刷新と、長期運用サービスの Kubernetes へのリアーキテクト
gree_tech
PRO
0
110
Featured
See All Featured
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.9k
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
740
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.4k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2.1k
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
560
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
2
2.2k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
250
1.3M
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.9k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
18k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2.2k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
56
3.5k
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