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

Testing: The Future... Today?

Testing: The Future... Today?

Discuss the current state of testing in Ember applications, where we are going, and what we can do now...

This talk was given on 2017-03-23 at Ember NYC.

Robert Jackson

March 23, 2017
Tweet

More Decks by Robert Jackson

Other Decks in Programming

Transcript

  1. Testing: The Future...
    Today?

    View full-size slide

  2. Who the heck is this guy?
    ● Ember Core Team
    ● General Open Source Addict
    ● LinkedIn
    twitter: rwjblue
    github: rwjblue

    View full-size slide

  3. What are we talking about?

    View full-size slide

  4. State of Testing Today
    ● Acceptance
    ● Integration
    ● Unit

    View full-size slide

  5. Acceptance
    moduleForAcceptance('Acceptance | login');
    test('visiting /login', function(assert) {
    visit('/login');
    andThen(function() {
    assert.equal(currentURL(), '/login');
    });
    });

    View full-size slide

  6. Integration
    moduleForComponent('pretty-color', {
    integration: true
    });
    test('button click', function(assert) {
    this.render(hbs`{{magic-title}}`);
    this.$('button').click();
    assert.equal(this.$().text(), 'This is Magic');
    });

    View full-size slide

  7. Unit
    moduleFor('route:application');
    test('perform the right query', function(assert) {
    let route = this.subject();
    let result = route._queryString();
    assert.equal(result, '?awesome=sauce');
    });

    View full-size slide

  8. Great, :shipit:

    View full-size slide

  9. ● Built for a globals world.
    ● “magic” `andThen`
    ● No ability to stub/mock services.
    Acceptance Test Issues

    View full-size slide

  10. ● Completely ignorant about async
    ● Manual triggering of user interaction
    ● Much less “tailored” API
    Unit/Integration Test Issues

    View full-size slide

  11. emberjs/rfcs#119

    View full-size slide

  12. ● Use `async` / `await`
    ● Consistent interface
    ● Extendability
    ● Backwards Compatibility
    Grand Testing Unification

    View full-size slide

  13. Acceptance
    moduleForAcceptance('Can see things');
    test('clicking redirects', async function(assert) {
    await visit('/things');
    let list = find('.thing-item');
    assert.equal(list.length, 5);
    await click('.thing-item[data-id=1]');
    assert.equal(this.currentRouteName,
    'other-thing');
    });

    View full-size slide

  14. Integration
    moduleForIntegration('post-display');
    test('expand when clicked', async function(assert) {
    await render(hbs`{{post-display}}`);
    await click('.post-item');
    assert.equal(
    this.find('.post-created-at').textContent,
    '2016-01-05'
    );
    });

    View full-size slide

  15. ● Overriding a service
    ● Registering custom test helpers
    ● Registering custom waiters
    ● Accessing service instances
    General Testing Concerns

    View full-size slide

  16. ● Hooks for work before/after tests.
    ● Accessing general test information
    General Testing Concerns

    View full-size slide

  17. Overriding a Service
    import Mock from 'somewhere/else';
    moduleForAcceptance('something', {
    beforeEach() {
    this.owner.register('service:stripe', Mock);
    }
    });

    View full-size slide

  18. Registering Custom Helpers
    import selectCategory from '../helpers/select-category';
    import loginAsAdmin from '../helpers/login-as-admin';
    moduleForAcceptance('Can see things', {
    beforeEach() {
    this.registerHelpers({
    loginAsAdmin, selectCategory
    });
    }
    });

    View full-size slide

  19. Custom Helpers
    // tests/helpers/login-as-admin';
    import { testHelper } from 'ember-qunit';
    export default testHelper(async function() {
    await this.click('.login-now');
    await this.fillIn('.username', 'rwjblue');
    await this.fillIn('.password', 'moar beerz plz');
    await this.click('.submit');
    });

    View full-size slide

  20. Custom Helpers
    import selectCategory from '../helpers/select-category';
    import loginAsAdmin from '../helpers/login-as-admin';
    moduleForAcceptance('Can see things', {
    beforeEach() {
    this.registerHelpers({
    loginAsAdmin, selectCategory
    });
    }
    });

    View full-size slide

  21. Custom Waiters
    import { testWaiter } from 'ember-test-helpers';
    import { hasPendingTransactions } from
    'app-name/services/transactions';
    export default testWaiter(function() {
    return hasPendingTransactions();
    });

    View full-size slide

  22. Custom Waiters
    import transactionWaiter from '../waiters/pending-transactions';
    test('stuff happens', async function(assert) {
    // Normally done in setup, but slides....
    this.registerWaiter(transactionWaiter);
    await click('.foo');
    // all pending transactions are completed here...
    });

    View full-size slide

  23. Accessing Services
    test('foo', function(assert) {
    this.store = this.owner.lookup('service:store');
    this.store.push(....);
    });

    View full-size slide

  24. General Hooks
    // */tests/configuration.js
    import { TestConfig } from 'ember-test-helpers';
    export default class extends TestConfig {
    beforeSuite() {}
    beforeEach(testType, testContext) {}
    afterEach(testType, testContext) {}
    }

    View full-size slide

  25. General Hooks
    // liquid-fire's addon-test-support/configuration.js
    import { TestConfig } from 'ember-test-helpers';
    import runningTransitionWaiter from './waiters/running-transition';
    import randoHelper from './helpers/rando';
    export default class extends TestConfig {
    beforeEach() {
    this.registerWaiter(runningTransitionWaiter);
    this.registerHelper('randoHelper', randoHelper);
    }
    }

    View full-size slide

  26. General Hooks
    // mirage's addon-test-support/configuration.js
    import { TestConfig } from 'ember-test-helpers';
    import setup from 'ember-cli-mirage/setup-server';
    export default class extends TestConfiguration {
    beforeEach() {
    this.server = setup(this.owner);
    }
    }

    View full-size slide

  27. Test Information
    import { testHelper } from 'ember-qunit';
    export default testHelper(function() {
    if (this.testInfo.type === 'acceptance') {
    // acceptance test stuff here
    } else {
    // integration/unit test stuff here
    }
    });

    View full-size slide

  28. What about today?

    View full-size slide

  29. ● Use `async` / `await`
    ● Consistent Helpers
    Today

    View full-size slide

  30. What about today?
    `async` / `await`

    View full-size slide

  31. Today: `async` / `await`
    ember new test-2-12-app
    cd test-2-12-app
    ember install ember-maybe-import-regenerator

    View full-size slide

  32. Today: `async` / `await`
    // .eslintrc.js
    // ...snip...
    parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module'
    },
    // ...snip...

    View full-size slide

  33. Today: `async` / `await`
    moduleForAcceptance('Acceptance | index');
    test('visiting /', async function(assert) {
    await visit('/');
    assert.equal(currentURL(), '/');
    });

    View full-size slide

  34. Today: `async` / `await`
    npm install --save-dev ember-cli@^2.13.0-beta.2
    ember install ember-cli-babel@^6.0.0-beta.9
    ember install
    ember-cli-htmlbars-inline-precompile@^0.4.0-beta.2

    View full-size slide

  35. Today: `async` / `await`
    // config/targets.js
    module.exports = {
    browsers: [
    'last 1 Chrome versions',
    ]
    };

    View full-size slide

  36. Today: `async` / `await`

    View full-size slide

  37. What about today?
    Consistent Helpers

    View full-size slide

  38. Today: Consistent Helpers

    View full-size slide

  39. Today: Consistent Helpers
    ember install ember-native-dom-helpers

    View full-size slide

  40. ● Uses standard DOM API’s
    ● Works in unit, integration, and acceptance
    ● Implements helpers from emberjs/rfcs#119
    Today: Consistent Helpers

    View full-size slide

  41. import {
    click,
    tap,
    fillIn,
    find,
    findAll,
    keyEvent,
    triggerEvent,
    waitUntil
    } from 'ember-native-dom-helpers';
    Today: Consistent Helpers

    View full-size slide

  42. Today: Consistent Helpers
    moduleForAcceptance('Acceptance | Sign up');
    test('Usage awaiting the world to settle', async function(assert) {
    await visit('/sign-up');
    await fillIn('.first-name', 'Chuck');
    await fillIn('.last-name', 'Berry');
    await click('.submit-btn');
    assert.ok(find('.welcome-msg'), 'There is a welcome banner');
    assert.equal(find('.welcome-msg-name'), 'Chuck');
    });

    View full-size slide

  43. Today: Consistent Helpers
    moduleForComponent('Integration | Component | my-component', {
    integration: true
    });
    test('I can interact with my component', async function(assert) {
    this.render(hbs`{{my-component}}`);
    await fillIn('.some-input');
    await click('.main-button');
    await keyEvent('.other-input', 'keyup', 40); // down arrow
    await triggerEvent('.some-drop-area', 'mouseenter');
    assert.ok(find('.result-of-event-happened'));
    assert.equal(findAll('.result-list-item').length, 3);
    })

    View full-size slide

  44. Today: Consistent Helpers
    test('using `waitUntil` to test unsettled state', async
    function(assert) {
    await visit('/signup-example');
    assert.ok(find('.signup-example-form'), 'The signup form is
    displayed');
    fillIn('.signup-example-form__email', '[email protected]');
    fillIn('.signup-example-form__password', '123123');
    fillIn('.signup-example-form__password-confirmation', '123123');
    let submitPromise = click('.signup-example-form__submit-btn');
    await waitUntil(() => find('.dashbord-loading-substate-header'));
    assert.equal(find('.dashbord-loading-substate-header').textContent.tri

    View full-size slide

  45. What about today soon?
    Simpler QUnit API

    View full-size slide

  46. Today: Simpler QUnit API
    import { module, test, buildIntegrationOptions } from 'ember-qunit';
    module('component:x-foo', buildIntegrationOptions({
    beforeEach() {
    // special setup stuff
    }
    }));
    test('renders', function(assert) {
    assert.expect(1);
    this.render(hbs`{{pretty-color name="red"}}`);
    assert.equal(this.$('.color-name').text(), 'red');
    });

    View full-size slide

  47. Today: Simpler QUnit API
    import { module, test, setupIntegrationTest } from 'ember-qunit';
    module('component:x-foo', function(hooks) {
    setupIntegrationTest(hooks);
    test('renders', function(assert) {
    assert.expect(1);
    this.render(hbs`{{pretty-color name="red"}}`);
    assert.equal(this.$('.color-name').text(), 'red');
    });
    });

    View full-size slide