LIFF Component Testing with Cypress

LIFF Component Testing with Cypress

B29f42636a5f1249b640473d49aa4514?s=128

LINE Developers Thailand

September 13, 2020
Tweet

Transcript

  1. 11:15 - 11:50 Traitanit Huangsri LIFF Component Testing with Cypress

    Senior Software Engineer, LINE Thailand
  2. None
  3. LIFF Component Testing with Cypress

  4. About Me • Nott (Traitanit Huangsri) • Software Engineer -

    LINE Thailand • Cypress Ambassador
  5. LIFF 2020 Updates

  6. > Mobile & External Browsers Supported > New APIs: shareTargetPicker,

    ready, getLineVersion etc. > npm install @line/liff // since LIFF 2.3 LIFF 2020 Brief Updates
  7. LET’S TALK ABOUT TESTING

  8. Let’s find the answer together! How Do You Test Your

    LIFF App ?
  9. UNIT TESTS INTEGRATION TESTS E2E TESTS Testing Pyramid Automated Testing

    Strategy
  10. Testing Pyramid UNIT TESTS INTEGRATION TESTS E2E TESTS Automated Testing

    Strategy
  11. High Fidelity, Low Speed Low Fidelity, Very Fast UNIT TESTS

    INTEGRATION TESTS E2E TESTS Automated Testing Strategy
  12. “It would be great if we have unit tests with

    Higher FIDELITY and Good SPEED”
  13. LET ME Introduce

  14. Free & Open Source (MIT License) A tool to quickly

    and reliably test anything that runs in a browser.
  15. Stateless Stateful Why Cypress ?

  16. $ npm install -D cypress

  17. All-in-one testing tool. Provides popular off-the-shelf Mocha, Sinon and Chai

    and more bundled test utilities $ npm install -D cypress
  18. Your App & Your Tests Same Run Loop Provisioning &

    Perform system-level tasks How Cypress Works ?
  19. Web Frameworks Compatibility

  20. Command Log Your App Cypress UI Test Runner

  21. Trace Error to your source Human Readable Error Messages Debuggability

  22. Time Travel

  23. Automatic Wait

  24. Realtime Reloads

  25. describe('Demo', () => { it('is demo', () => { cy.get('[data-testid=input]').click().type('Hello

    World'); cy.get('[data-testid=input]').should('have.text', 'Hello World'); }); }); Finding Elements Performing Actions Assertion Cypress API
  26. Not only End-to-End Tests But also Unit Tests With full

    capabilities of Cypress Features
  27. LIFF Component Testing with

  28. LIFF SDK 1. Dependency with LINE Platform 2. Emulate Opening

    LIFF from LINE App is so difficult 3. Negative Test ??? LIFF Testing Problems
  29. LIFF SDK LIFF Testing Problems

  30. > Cypress automatically includes Sinon.js for stub & spy methods

    > Expose it as Cypress.Sinon or cy.stub() and cy.spy() > Stub: a way to modify a function and control its behavior by your tests > Spy: Letting you capture and assert the function called with correct arguments Control LIFF SDK With Cypress
  31. Vue.js Cypress LIFF Let’s Get Started

  32. sh$ vue add cypress-experimental # vue-cli > 3 > A

    Cypress Test Helper to Unit Test Vue Components > Build on top of vue-test-utils > Runs natively on a real browser > Requires Cypress 4.5.0 or later, Node 8+ https://github.com/bahmutov/cypress-vue-unit-test Introducing Cypress Vue Unit Tests
  33. <template> <h1 data-testid="title">Hello {{ name }}!</h1> </template> <script> export default

    { name: 'Greetings', props: { name: { type: String, required: true } } } </script> Greetings.vue Basic Example
  34. Greetings.spec.js import { mount } from 'cypress-vue-unit-test' import Greetings from

    '../../src/components/Greetings.vue' describe('Greetings', () => { it('Works awesomely', () => { const name = 'Cypress'; mount(Greetings, { propsData: { name, }, }); cy.get('[data-testid=title]').should('exist') .and('have.text', `Hello ${name}!`); }); }); Basic Example (TEST)
  35. $ vue-cli-service test:component Component Test with Cypress

  36. App.vue created: async function () { try { await liff.init({

    liffId: process.env.VUE_APP_LINE_LIFF_ID, }); } catch (error) { this.$swal({ icon: 'error', title: '<h2 data-testid="errorTitle">LIFF init failed</h2>', html: `<span data-testid="errorMsg">${error.message}</span>`, }); } } Scenario 1: Stub LIFF initialization LIFF Component Testing Examples
  37. App.spec.js describe('App', () => { it('liff init success', () =>

    { cy.stub(liff, 'init').as('liffInitStub').resolves(); mount(App); cy.get('[data-testid="init"]').should('be.visible') .and('have.text', 'Init success'); cy.get('@liffInitStub').should('be.calledWithExactly', { liffId: process.env.VUE_APP_LINE_LIFF_ID }); }); }); Scenario 1: Stub LIFF initialization LIFF Component Testing Examples
  38. App.spec.js describe('App', () => { it('liff init success', () =>

    { cy.stub(liff, 'init').as('liffInitStub').resolves(); mount(App); cy.get('[data-testid="init"]').should('be.visible') .and('have.text', 'Init success'); cy.get('@liffInitStub').should('be.calledWithExactly', { liffId: process.env.VUE_APP_LINE_LIFF_ID }); }); }); Scenario 1: Stub LIFF initialization LIFF Component Testing Examples
  39. App.spec.js describe('App', () => { it('liff init success', () =>

    { cy.stub(liff, 'init').as('liffInitStub').resolves(); mount(App); cy.get('[data-testid="init"]').should('be.visible') .and('have.text', 'Init success'); cy.get('@liffInitStub').should('be.calledWithExactly', { liffId: process.env.VUE_APP_LINE_LIFF_ID }); }); }); Scenario 1: Stub LIFF initialization LIFF Component Testing Examples
  40. Display stub was called Scenario 1: Stub LIFF initialization Running

    Component Tests
  41. App.spec.js describe('App', () => { it('liff init failure', () =>

    { const errorMessage = 'Init Error!'; cy.stub(liff, 'init').as('liffInitFailure').rejects(new Error(errorMessage)); mount(App); cy.get('[data-testid="errorTitle"').should('be.visible') .and('have.text', 'LIFF init failed'); cy.get('[data-testid="errorMsg"]').should('be.visible') .and('have.text', errorMessage); }); }); Scenario 2: LIFF Init Failure Negative Test with LIFF SDK
  42. Scenario 2: LIFF Init Failure Negative Test with LIFF SDK

    App.spec.js describe('App', () => { it('liff init failure', () => { const errorMessage = 'Init Error!'; cy.stub(liff, 'init').as('liffInitFailure').rejects(new Error(errorMessage)); mount(App); cy.get('[data-testid="errorTitle"').should('be.visible') .and('have.text', 'LIFF init failed'); cy.get('[data-testid="errorMsg"]').should('be.visible') .and('have.text', errorMessage); }); });
  43. Scenario 2: LIFF Init Failure Negative Test with LIFF SDK

  44. async created() { await liff.ready; if (!liff.isLoggedIn()) { liff.login(); }

    else { const profile = await liff.getProfile(); this.displayName = profile.displayName; this.pictureUrl = profile.pictureUrl; } }, Profile.vue Authentication with LINE Login
  45. Scenario 3: Test Login with LINE Authentication with LINE Login

  46. describe('Profile', () => { beforeEach(() => { Cypress.sinon.replace(liff, 'ready', Promise.resolve());

    }); it('render displayName when logged-in', () => { const profile = { displayName: 'Test User', pictureUrl: '<picturlUrl>' } cy.stub(liff, 'isLoggedIn').as('stubIsLoggedIn').returns(true); cy.stub(liff, 'getProfile').as('stubGetProfile').resolves(profile); mount(Profile).then(() => { // access everything in vue instance expect(Cypress.vue.$data.pictureUrl).to.equal(profile.pictureUrl); expect(Cypress.vue.$data.displayName).to.equal(profile.displayName); }); cy.get('[data-testid="pictureUrl"]').should('have.attr', 'src', profile.pictureUrl); cy.get('[data-testid="name"]').should('have.text', `Name: ${profile.displayName}`); }); }); Profile.spec.js Authentication with LINE Login
  47. describe('Profile', () => { beforeEach(() => { Cypress.sinon.replace(liff, 'ready', Promise.resolve());

    }); it('render displayName when logged-in', () => { const profile = { displayName: 'Test User', pictureUrl: '<picturlUrl>' } cy.stub(liff, 'isLoggedIn').as('stubIsLoggedIn').returns(true); cy.stub(liff, 'getProfile').as('stubGetProfile').resolves(profile); mount(Profile).then(() => { // access everything in vue instance expect(Cypress.vue.$data.pictureUrl).to.equal(profile.pictureUrl); expect(Cypress.vue.$data.displayName).to.equal(profile.displayName); }); cy.get('[data-testid="pictureUrl"]').should('have.attr', 'src', profile.pictureUrl); cy.get('[data-testid="name"]').should('have.text', `Name: ${profile.displayName}`); }); }); Profile.spec.js Authentication with LINE Login
  48. describe('Profile', () => { beforeEach(() => { Cypress.sinon.replace(liff, 'ready', Promise.resolve());

    }); it('render displayName when logged-in', () => { const profile = { displayName: 'Test User', pictureUrl: '<picturlUrl>' } cy.stub(liff, 'isLoggedIn').as('stubIsLoggedIn').returns(true); cy.stub(liff, 'getProfile').as('stubGetProfile').resolves(profile); mount(Profile).then(() => { // access everything in vue instance expect(Cypress.vue.$data.pictureUrl).to.equal(profile.pictureUrl); expect(Cypress.vue.$data.displayName).to.equal(profile.displayName); }); cy.get('[data-testid="pictureUrl"]').should('have.attr', 'src', profile.pictureUrl); cy.get('[data-testid="name"]').should('have.text', `Name: ${profile.displayName}`); }); }); Profile.spec.js Authentication with LINE Login
  49. Scenario 3: Test Login with LINE Authentication with LINE Login

  50. Emulate Opening LIFF from LINE App export default { name:

    'Mobile', data() { return { isInClient: false, os: '', } }, created() { this.isInClient = liff.isInClient(); this.os = liff.getOS(); } }
  51. Mobile.spec.js describe('Mobile', () => { it('emulates running app in LINE

    App', () => { const ios = 'ios'; cy.stub(liff, 'isInClient').as('isInLineApp').returns(true); cy.stub(liff, 'getOS').as('os').returns(ios); cy.viewport(‘iphone-xr'); mount(Mobile).then(() => { expect(Cypress.vue.$data.isInClient).to.be.true; expect(Cypress.vue.$data.os).to.be.equal(ios); }); }); }); // emulate iPhone-XR resolution // assert Vue data property https://on.cypress.io/viewport Emulate Opening LIFF from LINE App
  52. Mobile.spec.js describe('Mobile', () => { it('emulates running app in LINE

    App', () => { const ios = 'ios'; cy.stub(liff, 'isInClient').as('isInLineApp').returns(true); cy.stub(liff, 'getOS').as('os').returns(ios); cy.viewport(‘iphone-xr'); mount(Mobile).then(() => { expect(Cypress.vue.$data.isInClient).to.be.true; expect(Cypress.vue.$data.os).to.be.equal(ios); }); }); }); // emulate iPhone-XR resolution // assert Vue data property https://on.cypress.io/viewport Emulate Opening LIFF from LINE App
  53. Scenario 4: Emulate Opening LIFF From LINE App Emulate Opening

    LIFF from LINE App
  54. sendMessages: async function () { if (liff.isLoggedIn()) { await liff.sendMessages([

    { type: 'text', text: 'Hello World', } ]); this.$swal({ icon: 'success', title: '<h2 data-testid="sent">Sent!</h2>', }); } } SendMessages.vue Integration with LINE Platform
  55. describe('Send Messages', () => { it('can send message with mock

    function', () => { cy.stub(liff, 'sendMessages').as('sendMessages').resolves(); cy.stub(liff, 'isLoggedIn').as('isLoggedIn').returns(true); mount(SendMessages); cy.get('[data-testid="sendMessages"').click(); cy.get('[data-testid="sent"]').should('have.text', 'Sent!'); cy.get('@sendMessages').should('be.calledWithExactly', [ { type: 'text', text: 'Hello World', } ]); }); }); SendMessages.spec.js Integration with LINE Platform
  56. describe('Send Messages', () => { it('can send message with mock

    function', () => { cy.stub(liff, 'sendMessages').as('sendMessages').resolves(); cy.stub(liff, 'isLoggedIn').as('isLoggedIn').returns(true); mount(SendMessages); cy.get('[data-testid="sendMessages"').click(); cy.get('[data-testid="sent"]').should('have.text', 'Sent!'); cy.get('@sendMessages').should('be.calledWithExactly', [ { type: 'text', text: 'Hello World', } ]); }); }); SendMessages.spec.js Integration with LINE Platform
  57. describe('Send Messages', () => { it('can send message with mock

    function', () => { cy.stub(liff, 'sendMessages').as('sendMessages').resolves(); cy.stub(liff, 'isLoggedIn').as('isLoggedIn').returns(true); mount(SendMessages); cy.get('[data-testid="sendMessages"').click(); cy.get('[data-testid="sent"]').should('have.text', 'Sent!'); cy.get('@sendMessages').should('be.calledWithExactly', [ { type: 'text', text: 'Hello World', } ]); }); }); SendMessages.spec.js Integration with LINE Platform
  58. Scenario 5: Send Message to Chat Integration with LINE Platform

  59. Feature vue-test-utils Cypress Running Test in Browser Time Travel Debugger

    Debuggability Via Node Debugger Via Browser DevTools Reporting Text Based Cypress Dashboard + Screenshots + Video Recorded Test Fidelity ⬇ Very Low (virtual DOM) ⬆ Higher + All native browser APIs Interaction with Elements Limited API Use any Cypress Command Mock Library Jest Mock Sinon.js Code Coverage Comparison
  60. Cypress React Unit Test https://github.com/bahmutov/cypress-react-unit-test Cypress Angularjs Unit Test https://github.com/bahmutov/cypress-angular-unit-test

    Not a Vue.Js Developer?
  61. https://fb.com/groups/cypressiothailand Join us Cypress.io Thailand

  62. Blog Available Today!

  63. None