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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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
72
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
130
ELS – The Ember Language Server
turbo87
0
270
Internationali[sz]ation: It's easy in Ember!
turbo87
1
430
EuregioCup 2018 - Selbstbriefing
turbo87
1
76
750 km FAI-Dreieck mit der Clubklasse
turbo87
3
80
High Level DOM Assertions for QUnit
turbo87
0
140
EuregioCup 2017 - Selbstbriefing
turbo87
1
59
Other Decks in Technology
See All in Technology
Amazon Bedrock AgentCore ワークショップ JAWS UG TOHOKU / amazon-bedrock-agentcore-workshop-jawsug-tohoku-2026
gawa
9
640
On-behalf-of Token exchange with AgentCore Identity
hironobuiga
2
140
Claude Code の Sandbox 機能を Anthropic Sandbox Runtime(srt) で試そう!/lets-play-anthropic-sandbox-runtime
tomoki10
1
530
2026TECHFRESH畢業分享會 - 葬送的通靈師:化系統與用戶雜訊成行動訊號
line_developers_tw
PRO
0
730
Agentic Web
dynamis
1
200
DevOps Agentで始めるAWS運用 〜フロンティアエージェントが変える運用の現場〜
nyankotaro
1
380
個人最適 から 全体最適 へ AI情報共有会・AIギルド・AI-DLC で進める カンリーの組織展開
rfdnxbro
0
2.2k
[モダンアプリ勉強会]今更聞けないGit/GitHub入門
tsukuboshi
0
360
"何を作るか"を任される エンジニアは、どう育つのか
yutaokafuji
1
580
2026TECHFRESH畢業分享會 - AI 時代的人生存檔點
line_developers_tw
PRO
0
740
白金鉱業Meetup_Vol.24_「AIエージェントは分けるほど良い」は本当か? / Is it true that “the more you divide AI agents, the better”?
brainpadpr
1
270
作って終わりにしない タイミーのセマンティックレイヤー育成の現在地
chanyou0311
3
2.1k
Featured
See All Featured
It's Worth the Effort
3n
188
29k
Odyssey Design
rkendrick25
PRO
2
690
From π to Pie charts
rasagy
0
200
YesSQL, Process and Tooling at Scale
rocio
174
15k
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
2
4k
Done Done
chrislema
186
16k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.4k
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
320
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.3k
The Spectacular Lies of Maps
axbom
PRO
1
800
Optimizing for Happiness
mojombo
378
71k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
1
2.7k
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