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

The Next Generation of Testing in Ember.js

The Next Generation of Testing in Ember.js

Tobias Bieniek

March 13, 2018
Tweet

More Decks by Tobias Bieniek

Other Decks in Technology

Transcript

  1. ⏰ andThen() The andThen helper will wait for all preceding

    asynchronous helpers to complete prior to progressing forward.
  2. 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')); });
  3. 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
  4. 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
  5. 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
  6. jQuery import { click, find } from 'ember-native-dom-helpers'; await click('button.submit');

    assert.equal(find('ul.posts li').textContent, 'My new post');
  7. jQuery import { click, find } from '@ember/test-helpers'; await click('button.submit');

    assert.equal(find('ul.posts li').textContent, 'My new post');
  8. Nested Modules describe('Galaxy', function() { describe('Earth', function() { describe('Portland', function()

    { it('is awesome at EmberConf', function() { // right? });aaa }); }); });
  9. 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 }); }); });
  10. 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 }); }); });
  11. 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()
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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()
  17. 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()
  18. 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
  19. 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()
  20. 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()
  21. 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
  22. 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()
  23. 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()
  24. 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
  25. 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
  26. 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']); });
  27. 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']); });
  28. 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
  29. 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"
  30. 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
  31. 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'); }
  32. 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
  33. 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!');
  34. 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
  35. 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');