Slide 1

Slide 1 text

TESTING AGAINST TIME MEANINGFUL TESTING IN EMBER APPS WHEN TIMING MATTERS @JJORDAN_DEV

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

EMBER.JS TIMES

Slide 4

Slide 4 text

EMBER.JS BERLIN MEETUP

Slide 5

Slide 5 text

WHY DO WE TEST? TESTING IN JAVASCRIPT APPLICATIONS

Slide 6

Slide 6 text

WHY DO WE TEST? MASTERING NEW TESTING FRAMEWORKS… …

Slide 7

Slide 7 text

WHY DO WE TEST? …BRINGS US CLOSER TO OUR ULTIMATE GOAL

Slide 8

Slide 8 text

WHY DO WE TEST? SERIOUSLY THOUGH…

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

WHY DO WE TEST? ! ✅ App Health:

Slide 11

Slide 11 text

WHY DO WE TEST? ! $ ✅ App Health:

Slide 12

Slide 12 text

WHY DO WE TEST? ! $ App Health: ⚠ ❓

Slide 13

Slide 13 text

WHY DO WE TEST? ! $ App Health: ⚠ ❓ ! + > 6 months ❓

Slide 14

Slide 14 text

WHY DO WE TEST? ! $ App Health: ❓ ! + > 6 months ❓ BUILDING WITHOUT TESTING ATTRACTS BUGS

Slide 15

Slide 15 text

WHY DO WE TEST? REGRESSION TESTING PREVENTS SUBSEQUENT DEVELOP SECURE SHIP

Slide 16

Slide 16 text

WHY DO WE TEST? TESTS CREATE DOCUMENTATION

Slide 17

Slide 17 text

WHY DO WE TEST? TESTS MAKE US SHIP CODE WITH CONFIDENCE ✍ = +

Slide 18

Slide 18 text

TESTING EMBER APPLICATIONS TESTING IN JAVASCRIPT APPLICATIONS

Slide 19

Slide 19 text

TESTING EMBER APPLICATIONS CONVENTION OVER CONFIGURATION +

Slide 20

Slide 20 text

TESTING EMBER APPLICATIONS CONVENTION OVER CONFIGURATION + ember g component my-component installing component create app/components/my-component.js create app/templates/components/my-component.hbs create tests/integration/components/my-component-test.js A dummy test file for free on every module generated

Slide 21

Slide 21 text

TESTING EMBER APPLICATIONS CONVENTION OVER CONFIGURATION + ember g component my-component // tests/integration/components/my-component-test.js // … test('it renders', function(assert) { // Set any properties with this.set('myProperty', 'value'); // Handle any actions with // this.on(‘myAction’, function(val) { ... }); this.render(hbs`{{my-component}}`); assert.equal(this.$().text().trim(), ''); });

Slide 22

Slide 22 text

TESTING EMBER APPLICATIONS ❤ UNIT + INTEGRATION TESTING WITH EMBER-QUNIT

Slide 23

Slide 23 text

TESTING EMBER APPLICATIONS A MORE FUNCTIONAL APPROACH TO TEST SETUP Simplified Ember Testing API RFC (Request for Comments): https://github.com/emberjs/rfcs/blob/master/text/0232-simplify-qunit-testing-api.md import { module, test } from 'qunit'; import { setupRenderingTest } from ‘ember-qunit'; import { render } from ‘ember-test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('x-foo', function(hooks) { setupRenderingTest(hooks); hooks.beforeEach(function() { // custom setup here }); test('renders', async function(assert) { assert.expect(1); await render(hbs`{{pretty-color name="red"}}`); assert.equal(this.$('.color-name').text(), 'red'); }); });

Slide 24

Slide 24 text

TESTING EMBER APPLICATIONS A FUNCTIONAL APPROACH TO TEST SETUP Simplified Ember Testing API RFC (Request for Comments): https://github.com/emberjs/rfcs/blob/master/text/0232-simplify-qunit-testing-api.md import { module, test } from 'qunit'; import { setupRenderingTest } from ‘ember-qunit'; import { render } from ‘ember-test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('x-foo', function(hooks) { setupRenderingTest(hooks); hooks.beforeEach(function() { // custom setup here }); test('renders', async function(assert) { assert.expect(1); await render(hbs`{{pretty-color name="red"}}`); assert.equal(this.$('.color-name').text(), 'red'); }); });

Slide 25

Slide 25 text

TESTING EMBER APPLICATIONS A FUNCTIONAL APPROACH TO TEST SETUP Simplified Ember Testing API RFC (Request for Comments): https://github.com/emberjs/rfcs/blob/master/text/0232-simplify-qunit-testing-api.md import { module, test } from 'qunit'; import { setupRenderingTest } from ‘ember-qunit'; import { render } from ‘ember-test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('x-foo', function(hooks) { setupRenderingTest(hooks); hooks.beforeEach(function() { // custom setup here }); test('renders', async function(assert) { assert.expect(1); await render(hbs`{{pretty-color name="red"}}`); assert.equal(this.$('.color-name').text(), 'red'); }); });

Slide 26

Slide 26 text

TESTING EMBER APPLICATIONS A FUNCTIONAL APPROACH TO TEST SETUP Simplified Ember Testing API RFC (Request for Comments): https://github.com/emberjs/rfcs/blob/master/text/0232-simplify-qunit-testing-api.md import { module, test } from 'qunit'; import { setupRenderingTest } from ‘ember-qunit'; import { render } from ‘ember-test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('x-foo', function(hooks) { setupRenderingTest(hooks); hooks.beforeEach(function() { // custom setup here }); test('renders', async function(assert) { assert.expect(1); await render(hbs`{{pretty-color name="red"}}`); assert.equal(this.$('.color-name').text(), 'red'); }); });

Slide 27

Slide 27 text

TESTING EMBER APPLICATIONS A FUNCTIONAL APPROACH TO TEST SETUP Simplified Ember Testing API RFC (Request for Comments): https://github.com/emberjs/rfcs/blob/master/text/0232-simplify-qunit-testing-api.md import { module, test } from 'qunit'; import { setupRenderingTest } from ‘ember-qunit'; import { render } from ‘ember-test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('x-foo', function(hooks) { setupRenderingTest(hooks); hooks.beforeEach(function() { // custom setup here }); test('renders', async function(assert) { assert.expect(1); await render(hbs`{{pretty-color name="red"}}`); assert.equal(this.$('.color-name').text(), 'red'); }); });

Slide 28

Slide 28 text

TESTING EMBER APPLICATIONS A FUNCTIONAL APPROACH TO TEST SETUP Simplified Ember Testing API RFC (Request for Comments): https://github.com/emberjs/rfcs/blob/master/text/0232-simplify-qunit-testing-api.md import { module, test } from 'qunit'; import { setupRenderingTest } from ‘ember-qunit'; import { render } from ‘ember-test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('x-foo', function(hooks) { setupRenderingTest(hooks); hooks.beforeEach(function() { // custom setup here }); test('renders', async function(assert) { assert.expect(1); await render(hbs`{{pretty-color name="red"}}`); assert.equal(this.$('.color-name').text(), 'red'); }); });

Slide 29

Slide 29 text

TESTING EMBER APPLICATIONS A FUNCTIONAL APPROACH TO TEST SETUP Simplified Ember Testing API RFC (Request for Comments): https://github.com/emberjs/rfcs/blob/master/text/0232-simplify-qunit-testing-api.md import { module, test } from 'qunit'; import { setupRenderingTest } from ‘ember-qunit'; import { render } from ‘ember-test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('x-foo', function(hooks) { setupRenderingTest(hooks); hooks.beforeEach(function() { // custom setup here }); test('renders', async function(assert) { assert.expect(1); await render(hbs`{{pretty-color name="red"}}`); assert.equal(this.$('.color-name').text(), 'red'); }); });

Slide 30

Slide 30 text

TESTING EMBER APPLICATIONS A FUNCTIONAL APPROACH TO TEST SETUP Simplified Ember Testing API RFC (Request for Comments): https://github.com/emberjs/rfcs/blob/master/text/0232-simplify-qunit-testing-api.md import { module, test } from 'qunit'; import { setupRenderingTest } from ‘ember-qunit'; import { render } from ‘ember-test-helpers'; import hbs from 'htmlbars-inline-precompile'; module('x-foo', function(hooks) { setupRenderingTest(hooks); hooks.beforeEach(function() { // custom setup here }); test('renders', async function(assert) { assert.expect(1); await render(hbs`{{pretty-color name="red"}}`); assert.equal(this.$('.color-name').text(), 'red'); }); });

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

FALSE NEGATIVES

Slide 34

Slide 34 text

TEST TIMEOUTS

Slide 35

Slide 35 text

OCCASSIONALLY PASSING TESTS

Slide 36

Slide 36 text

TESTING: HOW AND WHY

Slide 37

Slide 37 text

ASYNC OPERATIONS MAKING TESTS WAIT

Slide 38

Slide 38 text

EDITORIAL BOARD FOR EMBER.JS TIMES

Slide 39

Slide 39 text

WAITING FOR ASYNCHRONOUS OPERATIONS TESTING ROUTE TRANSITIONS

Slide 40

Slide 40 text

WAITING FOR ASYNCHRONOUS OPERATIONS TESTING ROUTE TRANSITIONS export default Route.extend({ model() { const store = this.get('store'); const repoFetches = this.get('reposList').map((repo) => { return store.findRecord('github-org', repo); }); return all(repoFetches); }, });

Slide 41

Slide 41 text

ACCEPTANCE TESTS WAITING FOR ASYNCHRONOUS OPERATIONS

Slide 42

Slide 42 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('visiting /overview', function(assert) { visit('overview'); assert.equal(currentURL(), '/overview'); });

Slide 43

Slide 43 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('visiting /overview', function(assert) { visit('overview'); assert.equal(currentURL(), '/overview'); });

Slide 44

Slide 44 text

WAITING FOR ASYNCHRONOUS OPERATIONS ASYNC ACCEPTANCE TEST HELPERS: VISIT export default function visit(app, url) { let router = app.__container__.lookup('router:main'); app.boot().then(() => { router.location.setURL(url); // ... }); // teardown work return app.testHelpers.wait(); }

Slide 45

Slide 45 text

WAITING FOR ASYNCHRONOUS OPERATIONS ASYNC ACCEPTANCE TEST HELPERS: VISIT export default function visit(app, url) { let router = app.__container__.lookup('router:main'); app.boot().then(() => { router.location.setURL(url); // ... }); // teardown work return app.testHelpers.wait(); }

Slide 46

Slide 46 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('visiting /overview', function(assert) { visit('overview'); andThen(function() { assert.equal(currentURL(), '/overview'); }); });

Slide 47

Slide 47 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('visiting /overview', function(assert) { visit('overview'); andThen(function() { assert.equal(currentURL(), '/overview'); }); });

Slide 48

Slide 48 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('visiting /overview', function(assert) { visit('overview'); andThen(function() { assert.equal(currentURL(), '/overview'); }); }); returns a Promise which fulfills once all async operations have resolved

Slide 49

Slide 49 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('visiting /overview', function(assert) { visit('overview'); andThen(function() { assert.equal(currentURL(), '/overview'); }); });

Slide 50

Slide 50 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('visiting /overview', function(assert) { visit('overview'); andThen(function() { assert.equal(currentURL(), '/overview'); }); }); waits for all previously initiated Promises to resolve

Slide 51

Slide 51 text

TEST PASS 1/1

Slide 52

Slide 52 text

WAITING FOR ASYNCHRONOUS OPERATIONS visit() click() fillIn() keyEvent() triggerEvent() export default function visit(app, url){ // … return app.testHelpers.wait(); } BUILT-IN ASYNCHRONOUS HELPERS FOR ACCEPTANCE TESTING

Slide 53

Slide 53 text

WAITING FOR ASYNCHRONOUS OPERATIONS ANY TIMERS || ANY SCHEDULED ITEMS IN RUN LOOP? ANY PENDING REQUESTS? ANY OTHER PENDING WAITERS? repeat each 10 ms wait()

Slide 54

Slide 54 text

WAITING FOR ASYNCHRONOUS OPERATIONS TESTING WITH EMBER-TEST-HELPERS Ember-test-selectors on Github: https://github.com/emberjs/ember-test-helpers

Slide 55

Slide 55 text

INTEGRATION TESTS WAITING FOR ASYNCHRONOUS OPERATIONS

Slide 56

Slide 56 text

WAITING FOR ASYNCHRONOUS OPERATIONS TESTING ASYNC USER INTERACTIONS

Slide 57

Slide 57 text

WAITING FOR ASYNCHRONOUS OPERATIONS TESTING ASYNC USER INTERACTIONS import Component from '@ember/component'; import { debounce } from '@ember/runloop'; export default Component.extend({ pull: null, actions: { loadComments(pull) { this.set('isLoadingComments', true); debounce(this, this.loadComments, pull, 800); }, }, });

Slide 58

Slide 58 text

WAITING FOR ASYNCHRONOUS OPERATIONS TESTING ASYNC USER INTERACTIONS import Component from '@ember/component'; import { debounce } from '@ember/runloop'; export default Component.extend({ pull: null, comments: null, loadComments(pull) { const commentsUrl = pull.get('commentsUrl'); return this.get(‘request’).fetch(commentsUrl).then((comments) => { this.set('comments', comments); this.set('isLoadingComments', false); }); }, actions: { loadComments(pull) { this.set('isLoadingComments', true); debounce(this, this.loadComments, pull, 800); }, }, });

Slide 59

Slide 59 text

WAITING FOR ASYNCHRONOUS OPERATIONS TESTING ASYNC USER INTERACTIONS import Component from '@ember/component'; import { debounce } from '@ember/runloop'; export default Component.extend({ pull: null, comments: null, loadComments(pull) { const commentsUrl = pull.get('commentsUrl'); return this.get(‘request’).fetch(commentsUrl).then((comments) => { this.set('comments', comments); this.set('isLoadingComments', false); }); }, actions: { loadComments(pull) { this.set('isLoadingComments', true); debounce(this, this.loadComments, pull, 800); }, }, });

Slide 60

Slide 60 text

WAITING FOR ASYNCHRONOUS OPERATIONS TESTING ASYNC USER INTERACTIONS import Service from '@ember/service'; export default Service.extend({ fetch(url) { // ajax request for urls }, });

Slide 61

Slide 61 text

WAITING FOR ASYNCHRONOUS OPERATIONS module('news-item', function(hooks) { setupRenderingTest(hooks); test(‘loading comments via user interaction', async function(assert) { // ember install ember-data-factory-guy this.set('pull', make('github-pull')); await this.render(hbs`{{news-item pull=pull repo=repo}}`); $(‘[data-test-load-comments]').click(); assert.equal($('[data-test-num-of-comments]').text(), `2`, 'displays comments’); } });

Slide 62

Slide 62 text

NOT OK

Slide 63

Slide 63 text

WAITING FOR ASYNCHRONOUS OPERATIONS module('news-item', function(hooks) { setupRenderingTest(hooks); test(‘loading comments via user interaction', async function(assert) { // ember install ember-data-factory-guy this.set('pull', make('github-pull')); await this.render(hbs`{{news-item pull=pull repo=repo}}`); $(‘[data-test-load-comments]').click(); assert.equal($('[data-test-num-of-comments]').text(), `2`, 'displays comments’); } });

Slide 64

Slide 64 text

WAITING FOR ASYNCHRONOUS OPERATIONS YARN ADD TESTDOUBLE

Slide 65

Slide 65 text

WAITING FOR ASYNCHRONOUS OPERATIONS module(‘news-item‘, function(hooks) { setupRenderingTest(hooks); hooks.beforeEach(function() { this.register('service:request', Service.extend({ fetch: td.function(), }); }); });

Slide 66

Slide 66 text

WAITING FOR ASYNCHRONOUS OPERATIONS module(‘news-item‘, function(hooks) { setupRenderingTest(hooks); hooks.beforeEach(function() { this.register('service:request', Service.extend({ fetch: td.function(), }); }); test('loading comments via user interaction', async function(assert) { const pull = make(‘github-pull') this.set('pull', pull); this.set('comments', makeList('github-comment', 2)); const commentsUrl = 'https://api.github.com/repos/user1/repository/pulls/1/comments'; td.when(this.get(‘request’).fetch(commentsUrl)) .thenResolve(this.comments); await render(hbs`{{news-item pull=pull repo=repo}}`); $(‘[data-test-load-comments]').click(); assert.equal($(‘[data-test-num-of-comments]’).text().trim(), `2`, 'displays comments'); }); });

Slide 67

Slide 67 text

TEXT NO CODE WALLS PLEASE

Slide 68

Slide 68 text

WAITING FOR ASYNCHRONOUS OPERATIONS module(‘news-item‘, function(hooks) { setupRenderingTest(hooks); hooks.beforeEach(function() { this.register('service:request', Service.extend({ fetch: td.function(), }); }); test('loading comments via user interaction', async function(assert) { const pull = make(‘github-pull') this.set('pull', pull); this.set('comments', makeList('github-comment', 2)); const commentsUrl = 'https://api.github.com/repos/user1/repository/pulls/1/comments'; td.when(this.get(‘request’).fetch(commentsUrl)) .thenResolve(this.comments); await render(hbs`{{news-item pull=pull repo=repo}}`); $(‘[data-test-load-comments]').click(); assert.equal($(‘[data-test-num-of-comments]’).text().trim(), `2`, 'displays comments'); }); });

Slide 69

Slide 69 text

WAITING FOR ASYNCHRONOUS OPERATIONS module(‘news-item‘, function(hooks) { setupRenderingTest(hooks); hooks.beforeEach(function() { this.register('service:request', Service.extend({ fetch: td.function(), }); }); test('loading comments via user interaction', async function(assert) { const pull = make(‘github-pull') this.set('pull', pull); this.set('comments', makeList('github-comment', 2)); const commentsUrl = 'https://api.github.com/repos/user1/repository/pulls/1/comments'; td.when(this.get(‘request’).fetch(commentsUrl)) .thenResolve(this.comments); await render(hbs`{{news-item pull=pull repo=repo}}`); $(‘[data-test-load-comments]').click(); assert.equal($(‘[data-test-num-of-comments]’).text().trim(), `2`, 'displays comments'); }); });

Slide 70

Slide 70 text

WAITING FOR ASYNCHRONOUS OPERATIONS app/services/request.js tests/integration/components/news-item-test.js import Service from '@ember/component'; export default Service.extend({ fetch(url) { // ajax request for urls }, }); const requestStub = Service.extend({ fetch: td.function(); });

Slide 71

Slide 71 text

WAITING FOR ASYNCHRONOUS OPERATIONS const requestStub = Service.extend({ fetch: td.function(); }); td.when(this.get(‘requestStub’) .fetch(commentsUrl)) .thenResolve(this.comments); loadComments(pull) { const commentsUrl = pull.get('commentsUrl'); return this.get(‘request’) .fetch(commentsUrl) .then((comments) => { this.set('comments', comments); this.set('isLoadingComments', false); }); }, app/components/news-item.js tests/integration/components/news-item-test.js

Slide 72

Slide 72 text

WAITING FOR ASYNCHRONOUS OPERATIONS td.when().thenResolve() td.when().thenReject() td.when().thenCallback() MOCKING PROMISES SYNCHRONOUSLY IMMEDIATE RESOLUTION / REJECTION OF MOCKED PROMISES

Slide 73

Slide 73 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('loading comments via user interaction', async function(assert) { const pull = make(‘github-pull') this.set('pull', pull); this.set('comments', makeList('github-comment', 2)); const commentsUrl = 'https://api.github.com/repos/user1/repository/pulls/1/comments'; td.when(this.get(‘request’) .fetch(commentsUrl)) .thenResolve(this.comments); await render(hbs`{{news-item pull=pull repo=repo}}`); $(‘[data-test-load-comments]’).click(); assert.equal($('[data-test-num-of-comments]').text(), `2`, 'displays comments'); });

Slide 74

Slide 74 text

NOT OK

Slide 75

Slide 75 text

WAITING FOR ASYNCHRONOUS OPERATIONS wait().then(callback) LETTING PROMISES AND TIMERS SETTLE POLLING FOR ANY PENDING REQUESTS AND ACTIONS IN RUN LOOP

Slide 76

Slide 76 text

WAITING FOR ASYNCHRONOUS OPERATIONS import { wait } from 'ember-test-helpers/wait'; test('loading comments via user interaction', async function(assert) { const pull = make(‘github-pull') this.set('pull', pull); this.set('comments', makeList('github-comment', 2)); const commentsUrl = 'https://api.github.com/repos/user1/repository/pulls/1/comments'; td.when(this.get(‘request’).fetch(commentsUrl)) .thenResolve(this.comments); await render(hbs`{{news-item pull=pull repo=repo}}`); $('[data-test-load-comments]').click(); return wait().then(() => { assert.equal($('[data-test-num-of-comments]').text(), `2`, 'displays comments'); }); });

Slide 77

Slide 77 text

WAITING FOR ASYNCHRONOUS OPERATIONS import { wait } from 'ember-test-helpers/wait'; test('loading comments via user interaction', await function(assert) { const pull = make(‘github-pull') this.set('pull', pull); this.set('comments', makeList('github-comment', 2)); const commentsUrl = 'https://api.github.com/repos/user1/repository/pulls/1/comments'; td.when(this.get(‘request’).fetch(commentsUrl)) .thenResolve(this.comments); await render(hbs`{{news-item pull=pull repo=repo}}`); $('[data-test-load-comments]').click(); return wait().then(() => { assert.equal($('[data-test-num-of-comments]').text(), `2`, 'displays comments'); }); });

Slide 78

Slide 78 text

WAITING FOR ASYNCHRONOUS OPERATIONS await fillIn() await click() await keyEvent() await triggerEvent() await focus() await blur() await tap() EMBER-NATIVE-DOM-HELPERS & AWAIT / ASYNC SUITABLE FOR BOTH YOUR ACCEPTANCE & INTEGRATION TESTS

Slide 79

Slide 79 text

WAITING FOR ASYNCHRONOUS OPERATIONS import { click } from 'ember-native-dom-helpers'; test('loading comments via user interaction', async function(assert) { this.set('pull', make('github-pull')); this.set('comments', makeList('github-comment', 2)); const commentsUrl = 'https://api.github.com/repos/user1/repository/pulls/1/comments'; td.when(this.get(‘request’).fetch(commentsUrl)) .thenResolve(this.comments); await render(hbs`{{news-item pull=pull repo=repo}}`); await click(‘[data-test-load-comments]’); assert.equal($('[data-test-num-of-comments]').text(), `2`, 'displays comments'); });

Slide 80

Slide 80 text

TEST PASS 2/2

Slide 81

Slide 81 text

WAITING FOR ASYNCHRONOUS OPERATIONS HANDLING ASYNC BEHAVIOUR WITH EMBER-CONCURRENCY Alex Matchneer: ember-concurrency: the solution to so many problems you never knew you had: https://emberway.io/ember-concurrency-the-solution-to-so-many-problems-you-never-knew-you-had-cce6d7731ba9

Slide 82

Slide 82 text

WAITING FOR ASYNCHRONOUS OPERATIONS export default Component.extend({ //… reloadComments: task(function * () { const commentsUrl = this.get('pull.commentsUrl'); const comments = yield this.get(‘request’) .fetch(this.get(‘pull.commentsUrl’)); this.set('comments', comments); }).drop(), }); GENERATOR FUNCTIONS WITH EMBER-CONCURRENCY

Slide 83

Slide 83 text

WAITING FOR ASYNCHRONOUS OPERATIONS export default Component.extend({ startReloading: task(function *(){ while(true) { this.get(‘reloadComments').perform(); yield timeout(50000); }, reloadComments: task(function * () { const commentsUrl = this.get('pull.commentsUrl'); const comments = yield this.get(‘request’) .fetch(this.get(‘pull.commentsUrl’)); this.set('comments', comments); }).drop(), });

Slide 84

Slide 84 text

WAITING FOR ASYNCHRONOUS OPERATIONS td.when(this.get(‘request’) .fetch(commentsUrl)) .thenResolve(this.comments); await this.render(hbs`{{news-item pull=pull repo=repo}}`); return wait().then(() => { assert.equal(find('[data-test-num-of-comments]').textContent.trim(), `2`, 'displays comments'); });

Slide 85

Slide 85 text

TEST TIMEOUT

Slide 86

Slide 86 text

WAITING FOR ASYNCHRONOUS OPERATIONS startReloading: task(function *(){ while(true) { this.get(‘reloadComments').perform(); if (Ember.testing) { return; } yield timeout(50000); } }

Slide 87

Slide 87 text

WAITING FOR ASYNCHRONOUS OPERATIONS startReloading: task(function *(){ while(true) { this.get(‘reloadComments').perform(); if (Ember.testing) { return; } yield timeout(50000); } }

Slide 88

Slide 88 text

WAITING FOR ASYNCHRONOUS OPERATIONS …CREATES A PERFECT HOME FOR BUGS

Slide 89

Slide 89 text

WAITING FOR ASYNCHRONOUS OPERATIONS AND WHAT IF WE NEED TO TEST SEVERAL ITERATIONS?

Slide 90

Slide 90 text

WAITING FOR ASYNCHRONOUS OPERATIONS await render(hbs`{{news-item pull=pull repo=repo}}`); later(() => { run.cancelTimers(); }, 500); return wait().then(() => { assert.equal(find('[data-test-num-of-comments]').textContent.trim(), `2`, 'displays comments'); });

Slide 91

Slide 91 text

WAITING FOR ASYNCHRONOUS OPERATIONS await render(hbs`{{news-item pull=pull repo=repo}}`); later(() => { run.cancelTimers(); }, 500); return wait().then(() => { assert.equal(find('[data-test-num-of-comments]').textContent.trim(), `2`, 'displays comments'); });

Slide 92

Slide 92 text

NOT OK

Slide 93

Slide 93 text

WAITING FOR ASYNCHRONOUS OPERATIONS await render(hbs`{{news-item pull=pull repo=repo}}`); later(() => { run.cancelTimers(); }, 500); return wait().then(() => { assert.equal(find('[data-test-num-of-comments]').textContent.trim(), `2`, 'displays comments'); });

Slide 94

Slide 94 text

WAITING FOR ASYNCHRONOUS OPERATIONS await render(hbs`{{news-item pull=pull repo=repo}}`); later(() => { run.cancelTimers(); }, 500); return wait().then(() => { assert.equal(find('[data-test-num-of-comments]').textContent.trim(), `2`, 'displays comments'); });

Slide 95

Slide 95 text

WAITING FOR ASYNCHRONOUS OPERATIONS await render(hbs`{{news-item pull=pull repo=repo}}`); later(() => { run.cancelTimers(); }, 500000); return wait().then(() => { assert.equal(find('[data-test-num-of-comments]').textContent.trim(), `2`, 'displays comments'); });

Slide 96

Slide 96 text

WAITING FOR ASYNCHRONOUS OPERATIONS

Slide 97

Slide 97 text

WAITING FOR ASYNCHRONOUS OPERATIONS const TIMEOUT_INTERVAL = Ember.testing ? 1 : 500000; //… startReloading: task(function *(){ while(true) { yield timeout(TIMEOUT_INTERVAL); this.get(‘reloadComments').perform(); } }

Slide 98

Slide 98 text

WAITING FOR ASYNCHRONOUS OPERATIONS await render(hbs`{{news-item pull=pull repo=repo}}`); later(() => { run.cancelTimers(); }, 500000); return wait().then(() => { assert.equal(find('[data-test-num-of-comments]').textContent.trim(), `2`, 'displays comments'); });

Slide 99

Slide 99 text

WAITING FOR ASYNCHRONOUS OPERATIONS await render(hbs`{{news-item pull=pull repo=repo}}`); later(() => { run.cancelTimers(); }, 50); return wait().then(() => { assert.equal(find('[data-test-num-of-comments]').textContent.trim(), `2`, 'displays comments'); });

Slide 100

Slide 100 text

await render(hbs`{{news-item pull=pull repo=repo}}`); later(() => { run.cancelTimers(); }, 50); return wait().then(() => { assert.equal(find('[data-test-num-of-comments]').textContent.trim(), `2`, 'displays comments'); });

Slide 101

Slide 101 text

TEST PASS 3/3

Slide 102

Slide 102 text

WAITING FOR ASYNCHRONOUS OPERATIONS FURTHER READING Ember Concurrency Docs on Testing: https://ember-concurrency.com/#/docs/testing-debugging Ember Testing Unificationn RFC: https://github.com/emberjs/rfcs/pull/119

Slide 103

Slide 103 text

TIME TRAVEL MAKING TESTS PASS AT ANY TIME

Slide 104

Slide 104 text

SETTING AND TRAVELLING THROUGH TIME

Slide 105

Slide 105 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('the overview page doesn\'t stress me out with release date disclaimers', function(assert) { visit('/overview'); andThen(function() { assert.notOk(find('[data-test-is-thursday-disclaimer]'), 'doesn\'t display Thu disclaimer'); assert.notOk(find('[data-test-is-friday-disclaimer]'), 'doesn\'t display Fri disclaimer'); }); });

Slide 106

Slide 106 text

NOT OK

Slide 107

Slide 107 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('the overview page doesn\'t stress me out with release date disclaimers', function(assert) { visit('/overview'); andThen(function() { assert.notOk(find('[data-test-is-thursday-disclaimer]'), 'doesn\'t display Thu disclaimer'); assert.notOk(find('[data-test-is-friday-disclaimer]'), 'doesn\'t display Fri disclaimer'); }); });

Slide 108

Slide 108 text

WAITING FOR ASYNCHRONOUS OPERATIONS moduleForAcceptance('Acceptance | overview', { beforeEach() Timecop.install(); Timecop.travel(new Date(2017, 9, 11, 11, 45)); }, afterEach(){ Timecop.uninstall(); } }); EMBER INSTALL EMBER-CLI-TIMECOP

Slide 109

Slide 109 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('the overview page doesn\'t stress me out with release date disclaimers', function(assert) { visit('/overview'); andThen(function() { assert.notOk(find('[data-test-is-thursday-disclaimer]'), 'doesn\'t display Thursday disclaimer'); assert.notOk(find('[data-test-is-friday-disclaimer]'), 'doesn\'t display Friday disclaimer'); }); }); moduleForAcceptance('Acceptance | overview', { beforeEach() Timecop.install(); Timecop.travel(new Date(2017, 9, 11, 11, 45)); // is a Wednesday: 11.10.2017 }, afterEach(){ Timecop.uninstall(); } });

Slide 110

Slide 110 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('the overview page doesn\'t stress me out with release date disclaimers', function(assert) { visit('/overview'); andThen(function() { assert.notOk(find('[data-test-is-thursday-disclaimer]'), 'doesn\'t display Thursday disclaimer'); assert.notOk(find('[data-test-is-friday-disclaimer]'), 'doesn\'t display Friday disclaimer'); }); }); moduleForAcceptance('Acceptance | overview', { beforeEach() Timecop.install(); Timecop.travel(new Date(2017, 9, 11, 11, 45)); // is a Wednesday: 11.10.2017 }, afterEach(){ Timecop.uninstall(); } });

Slide 111

Slide 111 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('the overview page doesn\'t stress me out with release date disclaimers', function(assert) { visit('/overview'); andThen(function() { assert.notOk(find('[data-test-is-thursday-disclaimer]'), 'doesn\'t display Thursday disclaimer'); assert.notOk(find('[data-test-is-friday-disclaimer]'), 'doesn\'t display Friday disclaimer'); }); }); moduleForAcceptance('Acceptance | overview', { beforeEach() Timecop.install(); Timecop.travel(new Date(2017, 9, 11, 11, 45)); // is a Wednesday: 11.10.2017 }, afterEach(){ Timecop.uninstall(); } });

Slide 112

Slide 112 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('the overview page doesn\'t stress me out with release date disclaimers', function(assert) { visit('/overview'); andThen(function() { assert.notOk(find('[data-test-is-thursday-disclaimer]'), 'doesn\'t display Thursday disclaimer'); assert.notOk(find('[data-test-is-friday-disclaimer]'), 'doesn\'t display Friday disclaimer'); }); }); moduleForAcceptance('Acceptance | overview', { beforeEach() Timecop.install(); Timecop.travel(new Date(2017, 9, 11, 11, 45)); // is a Wednesday: 11.10.2017 }, afterEach(){ Timecop.uninstall(); } });

Slide 113

Slide 113 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('the overview page…oh no, it’s Friday’, function(assert) { Timecop.travel(new Date(2017, 9, 13, 16, 20)); // is a Friday visit('/overview'); andThen(function() { assert.notOk(find('[data-test-is-thursday-disclaimer]'), 'doesn\'t display Thursday disclaimer'); assert.ok(find('[data-test-is-friday-disclaimer]'), ‘the newsletter has to get out - oh no!’); }); });

Slide 114

Slide 114 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('the overview page…oh no, it’s Friday’, function(assert) { Timecop.travel(new Date(2017, 9, 13, 16, 20)); // is a Friday visit('/overview'); andThen(function() { assert.notOk(find('[data-test-is-thursday-disclaimer]'), 'doesn\'t display Thursday disclaimer'); assert.ok(find('[data-test-is-friday-disclaimer]'), ‘the newsletter has to get out - oh no!’); }); });

Slide 115

Slide 115 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('the overview page…oh no, it’s Friday’, function(assert) { Timecop.travel(new Date(2017, 9, 13, 16, 20)); // is a Friday visit('/overview'); andThen(function() { assert.notOk(find('[data-test-is-thursday-disclaimer]'), 'doesn\'t display Thursday disclaimer'); assert.ok(find('[data-test-is-friday-disclaimer]'), ‘the newsletter has to get out - oh no!’); }); });

Slide 116

Slide 116 text

WAITING FOR ASYNCHRONOUS OPERATIONS test('the overview page…oh no, it’s Friday’, function(assert) { Timecop.travel(new Date(2017, 9, 13, 16, 20)); // is a Friday visit('/overview'); andThen(function() { assert.notOk(find('[data-test-is-thursday-disclaimer]'), 'doesn\'t display Thursday disclaimer'); assert.ok(find('[data-test-is-friday-disclaimer]'), ‘the newsletter has to get out - oh no!’); }); });

Slide 117

Slide 117 text

TEST PASS 4/4

Slide 118

Slide 118 text

TIMING IN YOUR TESTS MATTERS

Slide 119

Slide 119 text

TIMING IN YOUR TESTS MATTERS

Slide 120

Slide 120 text

BARDZO DZIĘKUJĘ! @JESSICA @JJORDAN_DEV