Testing UI with EmberJS

Martin Nuc

@martin_nuc

Topics

  • why Ember?
  • tests in Ember
  • page objects
  • Ember goodies
  • what are we up to next?

Why Ember?

Test pyramid

Acceptance

Integration

Unit

moduleFor('service:discussion', 'Unit | Service | discussion', {
  needs: ['service:ajax']
});

test('it deletes question', function(assert) {
  assert.expect(3);

  let service = this.subject({
    record: EmberObject.create({ id: '1' }),
    ajax: EmberObject.create({
      put(url, { data }) {
        assert.equal(url, 'loans/1/questions/2');
        assert.deepEqual(data, { deleted: true });
        return resolve();
      },
      buildURL(url) {
        return url;
      }
    }),
    refetchQuestions() {
      assert.ok(true);
      return resolve();
    }
  });

  let question = EmberObject.create({ id: '2' });

  return service.deleteQuestion({ question });
});

Integration

test('it renders question with answer', function(assert) {
  this.set('question', {
    message: 'How are you?',
    isActive: true,
    isAnswered: true,
    answer: {
      isActive: true,
      message: 'Donuts'
    }
  });

  component.render(hbs`
    {{marketplace-discussion-question question=question}}
  `);

  assert.equal(component.message.text.trim(), 'How are you?');
  assert.equal(component.answer.message.text.trim(), '- Donuts');
});

Acceptance


test('Marge can delete her question at discussion on marketplace detail', async assert => {
  await login.visit().authenticate({ email: 'marge@zonkej.cz' });

  await detail.visit({ purpose: 'auto-moto', slug: 'marketplace-#1-1' });
  assert.equal(detail.discussion.status.show.text.trim(), 'Dotazy (10)');

  await detail.discussion.status.show.click();
  await detail.discussion.panel.deleteQuestionAt(1);
  assert.equal(
    detail.discussion.panel.questions(1).message.text.trim(),
    'Lorem ipsum...'
  );

  await detail.discussion.status.hide.click();
  assert.equal(detail.discussion.status.show.text.trim(), 'Dotazy (9)');
});

QUnit

Page object

Page object

import detail from 'zonky-app/tests/pages/marketplace/primary/detail';

...

assert.equal(detail.discussion.status.show.text.trim(), 'Dotazy (10)');

zonky-app/tests/pages/marketplace/primary/detail:

import detail from 'zonky-app/tests/pages/components/marketplace-detail';

export default create(
  Object.assign({}, detail, {
    visit: visitable('/:purpose/:slug')
  })
);

Page object tree

zonky-app/tests/pages/components/marketplace-detail.js:

import header from 'zonky-app/tests/pages/components/application-header';
import navigation from 
  'zonky-app/tests/pages/components/marketplace-detail-navigation';
...

export default {
  header,

  index: {
    scope: '[data-test-marketplace-detail="index"]'
  },

  category: {
    scope: '[data-test-marketplace-detail="category"]'
  },

  navigation,

  ...
}

Mirage

Mirage runs in browser!

  • everything one on page refresh
  • possible to run in every browser (inc. mobile)
  • for both running app and automated tests
  • doesn't wait for backend

Mirage

mirage.get('/loans/:id/investments', ({ db }, request) => {
  const recordId = request.params.id;

  return A(db.marketplaceInvestments.where({ recordId })).map(item => {
    delete item.recordId;
    return item;
  });
});

Browserstack

Browserstack

  • easy thanks to plugins
  • avoid unsupported functions

What's next?

Percy.io

Image diff

Visual regression testing

  • pixel perfect
  • covers visual changes
  • part of code review

Thank you

 

Questions?

UI testing in EmberJS

By Martin Nuc

UI testing in EmberJS

  • 391