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
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
74
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
アクセスキーが漏れた日にやるべきこと- 無効化の先にある本当の対応
kazzpapa3
0
330
Bill One 開発エンジニア 紹介資料
sansan33
PRO
7
20k
Eight Engineering Unit 紹介資料
sansan33
PRO
3
8.3k
1.5時間を無駄にして学んだwsl2におけるaptとsnapの選択と仕組み
yosaka0123
0
450
AWSとGitHub Actionsの責任境界と 組織で安全に使用する取り組み
nealle
0
160
AI駆動開発はどこまで来たのか? ファインディの最新実態調査で読み解く現在地 Devin Con Tokyo
akiratom
2
1.3k
AWS Blocks が楽しい #ゆるWeb札幌
tacck
PRO
0
150
AI開発に用いられるHPC技術について
gpuunite_official
0
290
AIで変わるエンジニアの働き方(仮)
naoinaoi
0
550
研究開発部の紹介 / Sansan R&D Profile
sansan33
PRO
4
24k
型落ちシンクライアント端末のPoEモジュールを自作したかった話
logica0419
0
450
AIに持続⼒を与える 判断の⻑期記憶設計
eiei114
1
720
Featured
See All Featured
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
1
2.1k
BBQ
matthewcrist
89
10k
Building a Scalable Design System with Sketch
lauravandoore
463
34k
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
220
Writing Fast Ruby
sferik
630
63k
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
450
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2k
Mind Mapping
helmedeiros
PRO
1
330
The World Runs on Bad Software
bkeepers
PRO
72
12k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
500
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.3k
How to Ace a Technical Interview
jacobian
281
24k
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