Slide 1

Slide 1 text

Mock Native API in your E2E test 2017/11/06 ElectronMeetup in Tokyo LT @joe_re

Slide 2

Slide 2 text

Who am I? twitter: @joe_re github: @joe­re community: Nishinippori.rb, GraphQL Tokyo Organizer

Slide 3

Slide 3 text

Book

Slide 4

Slide 4 text

Applications CafePitch Markdown Driven PresentationTool Tubutler Simple and usefull Youtube player

Slide 5

Slide 5 text

Today, I talk about spectron E2E test in case of using 'Native API' in your spec.

Slide 6

Slide 6 text

Spectron

Slide 7

Slide 7 text

What is Spectron? E2E Testing tool for Electron appplication. You can call ElectronAPI in your spec via ChromeDriver and WebDriver.io.

Slide 8

Slide 8 text

Example const app = new spectron.Application({ path: 'path/to/your/app' }); describe('application launch', function () { this.timeout(10000); beforeEach(function () { return app.start(); }); afterEach(function () { return app.stop(); }); it('shows an initial window', function () { return app.client.getWindowCount().then(function (count) { assert.equal(count, 1); }); }); });

Slide 9

Slide 9 text

Strong Points You can access any DOM via Webdriver.io. You can call any Electron API in your spec. No need setup script.(use Chromium inside Electron) Support CI services.(Travis, AppVeyor, etc)

Slide 10

Slide 10 text

DEMO

Slide 11

Slide 11 text

Spectron is nice. But it can't access Native API. (latest v3.7.2) So it can't mock Menu, Dialog, etc modules... https://github.com/electron/spectron/issues/94

Slide 12

Slide 12 text

This is simple solution, but ugly... if (process.env.NODE_ENV === "production") { // production code here } else { // test code here }

Slide 13

Slide 13 text

I want to write a spec which doesn't affect production code.

Slide 14

Slide 14 text

I created two modules to resolve it. spectron­fake­menu https://github.com/joe­re/spectron­fake­menu spectron­fake­dialog https://github.com/joe­re/spectron­fake­dialog

Slide 15

Slide 15 text

Usage spectron­fake­menu const Application = require('spectron').Application; const fakeMenu = require('spectron-fake-menu'); const app = new Application({ path: electron, args: [ path.join(__dirname, '.') ] }); fakeMenu.apply(app); // apply fake menu await app.start(); fakeMenu.clickMenu('Config'); // 'Config' Menu click fakeMenu.clickMenu('File', 'CloseTab'); // File->CloseTab Menu click spectron­fake­dialog const Application = require('spectron').Application; const fakeDialog = require('spectron-fake-dialog'); const app = new Application({ path: electron, args: [ path.join(__dirname, '.') ] }); fakeDialog.apply(app); await app.start(); fakeDialog.mock([ { method: 'showOpenDialog', value: ['faked.txt'] } ])); // write your specs

Slide 16

Slide 16 text

How do they work? Electron provides '­­require' option same as Node.js. It can preload a module before run main script. These modules inject extra IPC and provide API which call or mock Native API inside your specs.

Slide 17

Slide 17 text

Rough image

Slide 18

Slide 18 text

Implementation(spectron­fake­dialog) const path = require('path'); let _app = null; function apply(app) { _app = app; _app.args.unshift(path.join(__dirname, 'preload.js')); _app.args.unshift('--require'); return _app; } function mock(options) { return _app.electron.ipcRenderer.sendSync('SPECTRON_FAKE_DIALOG/SEND', options); } module.exports = { apply, mock };

Slide 19

Slide 19 text

Implementation(spectron­fake­dialog) preload script const { dialog, ipcMain, BrowserWindow } = require('electron'); //... some logics for creating mocked function ... function fake(options) { options.forEach(v => { if (dialog[v.method]) { dialog[v.method] = mockFunction.bind(null, v.value); } else { throw new Error(`can't find ${v.method} on dialog module.`); } }); } ipcMain.on('SPECTRON_FAKE_DIALOG/SEND', (e, options) => { fake(options); e.returnValue = true; });

Slide 20

Slide 20 text

Please use they if you'd like.

Slide 21

Slide 21 text

Thank you for your attention!