Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Assert(js) 2018: Testing Against Time in JavaSc...

Jessy Jordan
February 22, 2018

Assert(js) 2018: Testing Against Time in JavaScript Apps

20 min talk about common pitfalls and how to handle them when testing against time-dependent behaviour in JavaScript applications.
Presented Assert(js) Conf 2018, San Antonio, USA.

Jessy Jordan

February 22, 2018
Tweet

More Decks by Jessy Jordan

Other Decks in Programming

Transcript

  1. Testing against time in JavaScript Applications Assert (js) 2018 @

    J J O R D A N _ D E V J E S S I C A J O R D A N
  2. JavaScript Apps are full of asynchronous behaviour Events & User

    Interaction Timeouts Ajax Requests …and many more
  3. + +

  4. import { module, test } from 'qunit'; module('some-thing', function(hooks) {

    test('it computes foo', function(assert) { }); });
  5. import { module, test } from 'qunit'; module('some-thing', function(hooks) {

    hooks.beforeEach(function(){ // custom setup here }); hooks.afterEach(function(){ // custom teardown here }); // test(‘test1', …); // test(‘test2’, …); // test(‘test3’,…); });
  6. import { module, test } from 'qunit'; import { setupTest

    } from 'ember-qunit'; module('some-thing', function(hooks) { setupTest(hooks); test('it computes computedFoo', async function(assert) { }); });
  7. import { module, test } from 'qunit'; import { setupTest

    } from 'ember-qunit'; module('some-thing', function(hooks) { setupTest(hooks); test('it computes computedFoo', async function(assert) { const someThing = this.owner.lookup('service:some-thing'); }); });
  8. import { module, test } from 'qunit'; import { setupTest

    } from 'ember-qunit'; module('some-thing', function(hooks) { setupTest(hooks); test('it computes computedFoo', async function(assert) { const someThing = this.owner.lookup('service:some-thing'); someThing.set('foo', 'baz'); assert.equal(someThing.get('computedFoo'), 'computed baz'); }); });
  9. Why is it async? Routing Loading data for a view

    Handling route transitions Provide context for a view
  10. Why is it async? Routing Loading data for a view

    Handling route transitions Provide context for a view
  11. Ember Data Github Addon Documentation: https://github.com/elwayman02/ember-data-github#ember-data-github Loading Records in Ember

    Applications via Ember-Data: https://guides.emberjs.com/v3.0.0/routing/specifying-a-routes-model/ export default Route.extend({ model() { const store = this.get('store'); return store.query('github-pull', { repo: 'emberjs/website' }); }, });
  12. High Level DOM Assertions With Qunit Dom: https://github.com/simplabs/qunit-dom DOM Assertions

    with QUnit-DOM test('the main page displays a list of PRs', function(assert) { ourOwnVisitHelper('main'); assert.dom('.list-item').hasText('WIP: Fix Tomster Logo'); });
  13. ❌ test('the main page displays a list of PRs', function(assert)

    { ourOwnVisitHelper('main'); assert.dom('.list-item').hasText('WIP: Fix Tomster Logo'); });
  14. test('the main page displays a list of PRs', function(assert) {

    ourOwnVisitHelper('main'); await timeout(300); assert.dom('.list-item').hasText('WIP: Fix Tomster Logo'); });
  15. ❌ ✅ test('the main page displays a list of PRs',

    async function(assert) { ourOwnVisitHelper('main'); await timeout(300); assert.dom('.list-item').hasText('WIP: Fix Tomster Logo'); });
  16. ✅ but… test('the main page displays a list of PRs',

    async function(assert) { ourOwnVisitHelper('main'); await timeout(300000000000000000000000000000000000000000); assert.dom('.list-item').hasText('WIP: Fix Tomster Logo'); });
  17. High Level DOM Assertions With Qunit Dom: https://github.com/simplabs/qunit-dom test('the main

    page displays a list of PRs', function(assert) { ourOwnVisitHelper('main'); assert.dom('.list-item').hasText('WIP: Fix Tomster Logo'); });
  18. import { visit } from '@ember/test-helpers'; // ... test('the main

    page displays a list of PRs’, function(assert) { visit('main'); assert.dom('.list-item').hasText('WIP: Fix Tomster Logo'); });
  19. import { visit } from '@ember/test-helpers'; // ... test('the main

    page displays a list of PRs', async function(assert) { await visit('main'); assert.dom('.list-item').hasText('WIP: Fix Tomster Logo'); });
  20. ✅ import { visit } from '@ember/test-helpers'; // ... test('the

    main page displays a list of PRs', async function(assert) { await visit('main'); assert.dom('.list-item').hasText('WIP: Fix Tomster Logo'); });
  21. …will be waiting for async behaviour to settle Helpers for

    DOM Interaction & more… visit() click() fillIn() keyEvent() triggerEvent() … export default function visit(url){ //... return settled(); }
  22. se ttl ed function settled( ) { } hasRunLoop? hasPendingTime

    rs? hasPendingWait ers? hasPendingReq uests?
  23. function settled( ) { } hasRunLoop? hasPendingTime rs? hasPendingWait ers?

    hasPendingReq uests? se ttl ed ✅ ✅ ✅ ✅ resolved Promise
  24. Writing your own waiting helper See also waitUntil helper from

    @ember/test-helpers function myOwnWaitHelper(callback) { return new Promise(function(resolve, reject) { let time = 0; // you could of course also have a timer that tracks when this should time out and just reject function checkAssertion(currentTime) { setTImeout(function() { let value = callback(); time += 10; if (value) { // resolve if assertion is correct resolve(value); } else if (timerIsNotUpYet) { checkAssertion(time); } else { reject(value); } }, 10); } checkAssertion(0); }); }
  25. Writing your own waiting helper See also waitUntil helper from

    @ember/test-helpers function myOwnWaitHelper(callback) { return new Promise(function(resolve, reject) { let time = 0; // you could of course also have a timer that tracks when this should time out and just reject function checkAssertion(currentTime) { setTImeout(function() { let value = callback(); time += 10; if (value) { // resolve if assertion is correct resolve(value); } else if (timerIsNotUpYet) { checkAssertion(time); } else { reject(value); } }, 10); } checkAssertion(0); }); }
  26. Writing your own waiting helper See also waitUntil helper from

    @ember/test-helpers function myOwnWaitHelper(callback) { return new Promise(function(resolve, reject) { let time = 0; // you could of course also have a timer that tracks when this should time out and just reject function checkAssertion(currentTime) { setTImeout(function() { let value = callback(); time += 10; if (value) { // resolve if assertion is correct resolve(value); } else if (timerIsNotUpYet) { checkAssertion(time); } else { reject(value); } }, 10); } checkAssertion(0); }); }
  27. test('loading comments', async function(assert) { this.owner.register(‘service:request', RequestService.extend({ fetch: td.function(), });

    td.when(this.get('request').fetch(commentsUrl)) .thenResolve(this.mockComments); await render(hbs`{{pull-request-item pullRequest=pullRequest}}`); await click('button.load-comments'); assert.dom('.list-item .comment').hasText('Comment 1: LGTM!'); });
  28. ✅ test('loading comments', async function(assert) { this.owner.register(‘service:request', RequestService.extend({ fetch: td.function(),

    }); td.when(this.get('request').fetch(commentsUrl)) .thenResolve(this.mockComments); await render(hbs`{{pull-request-item pullRequest=pullRequest}}`); await click('button.load-comments'); assert.dom('.list-item .comment').hasText('Comment 1: LGTM!'); });
  29. works when run on Mondays… ✅ test('shows the week day',

    async function(assert) { await visit('main'); assert.dom('aside.week-day').hasText('Monday'); });
  30. …and fails Tuesday - Sunday ❌ test('shows the week day',

    async function(assert) { await visit('main'); assert.dom('aside.week-day').hasText('Monday'); });
  31. hooks.beforeEach(function() Timecop.install(); Timecop.travel(new Date(2017, 9, 11, 11, 45)); // is

    a Monday }); hooks.afterEach(function(){ Timecop.uninstall(); });
  32. ✅ module('Acceptance | overview', function(hooks) { hooks.beforeEach() Timecop.install(); Timecop.travel(new Date(2017,

    9, 11, 11, 45)); // is a Monday }, hooks.afterEach(){ Timecop.uninstall(); } test('shows the weekday', async function(assert) { await visit('main'); assert.dom('aside.week-day').hasText('Monday'); }); });
  33. Thank you. @ J J O R D A N

    _ D E V GITHUB:JESSICA-JORDAN @Assert(js) 2018