開発

Spectronでのdialogのテスト

spectronでテストを書いてますがちょっと躓いております。

変更対象のファイルを見るためにdialogを開いてフォルダを指定すると対象ファイル一覧が出てくる動きが実装されているのですが、spectronはdialogの操作がどうもできないようです。
Electronのdialogはネイティブのdialogを呼び出すようなのでハンドリングできないのかもしれません。

では他の人達はどうやっているのかなと検索してみるとspectron-fake-dialogというズバリなnpmがでてきました。どうやらMenuもspectronで操作できないようで同じ作者の方がそちらも作っています。

似たようなnpmにspectron-dialog-addonとかspectron-menu-addonも見つかったのですがTypeScriptで書き直しているだけのようなのでどちらを使っても良さそうです。

このnpmを使えばテストできそうですけど単純にdialogをstubできないかなと、ふと思ってしまい、そういえばFuseBoxのようなバンドラーを使う場合、どうすればよいのか?という疑問が湧いてきてしまったのでまずはFuseBoxでのstubのやり方を調べてみました。

前置き長くなりましたがFuseBoxでのstubの話になります。

まず調べてみるとrequireでインポートしてくるようなものを乗っ取るにはrewireproxyquireというものがありました。で、やはりwebpackのようなバンドラーを使う場合は専用のloaderだったりが用意されていて、これらをそのまま使うことはできなさそうです。

ということでまずはFuseBoxの方から調べていくのが良さそう。

またも検索技術を駆使して調べてみるとFuseBoxで使えるプラグインproxyrequireが見つかりました。FuseBoxだけでなくWebpackもサポートしているようです。

しかしこのproxyrequire、同様な機能のproxyquireと名前が似すぎていて混乱しました。インストールするnpmを間違えたりproxyrequireで検索するとproxyquireの情報が出てきたり、ページタイトルがproxyrequireになっているのにproxyquireについて書かれた内容だったり気づくまで時間がかかってしまった。

そして公式の説明をみるとproxyquireを使ったことがある人前提で書かれているのかさっぱりしていてよくわからなかったのでサンプルを作って動きを見てみました。

とりあえず適当な2つのTypeScriptを書いてみる。

bar.ts

export class Bar {
  readonly str: string;

  constructor(str: string) {
    this.str = str;
  }
  get_str(): string {
    return this.str;
  }
}

export const bar = new Bar('Hell world');

foo.ts

import {bar} from './bar';

export const foo = (str: string) => {
  const ret = bar.get_str();
  console.log(ret);
  return ret;
};

barをstubしたいのですがバンドルのエントリーファイルにプラグインの設定があるとだめみたいなので別途エントリーファイルを作成(一緒にできる回避策も公式の説明にあり)

index.ts

require('proxyrequire').registerGlobals();
import {foo} from './foo';
const app = foo;
export {app};

require(‘proxyrequire’).registerGlobals();がstubするためのおまじないです。
あとはFuseBoxの設定ファイルにも追加です。
ポイントだけ書いておきます。

fuse.js

.....
} = require('fuse-box/es6');
const StubPlugin = require('proxyrequire').FuseBoxStubPlugin(/foo.ts/);
.....
plugins: [
  EnvPlugin({NODE_ENV: isProduction ? 'production' : 'development'}),
  StubPlugin,
  [SassPlugin(), CSSPlugin()],
.....
const app = fuse.bundle('renderer').instructions(`> [index.ts]`);
.....

FuseBoxStubPlugin(/foo.ts/)の行でプラグイン作成になります。引数で適用するソースファイルのフィルターをしており、この場合はfoo.ts内のbarがstub化されます(他にrequireがあれば対象になる)。エントリーファイルは含まないようにしましょう。
plugins: でStubPluginを呼ぶように追加します。

あとはテストを書くだけです。AVAで書いてみました。

test.js

import test from 'ava';
import {proxy} from 'proxyrequire';
import * as app from '../renderer.js';

test(async (t) => {
  let res = '';

  // Before mocking
  res = app.FuseBox.main('default/index.js').app();
  t.is(res, 'Hell world');

  class Mock_bar {
    get_str() {
      return 'fake';
    }
  }

  // Mocking
  const Stub = proxy(() => require('../renderer.js'), {
    './bar': {bar: new Mock_bar()},
  });

  res = app.FuseBox.main('default/index.js').app();

  // After mocking
  t.not(res, 'Hell world');
  t.is(res, 'fake');
});

Mockする前の期待値はHello worldです。Mock_barがstubです。置き換えの書き方はproxyquireと同じように書きます。Mockしたあとはfakeが期待値です。想定通りにテストをパスしたので次回はSpectronで試してみたいと思います。

追記

FuseBoxのプラグインproxyrequireでElectronのAPIをstubできないかと試行錯誤したのですがうまくいきませんでした。どうやら動的にstubした際にElectronのライブラリも再読込する動きのようで、再読込時に既存のインスタンスが作り直しになって破棄されて結果死んでしまうようです。

ということで素直にspectron-dialog-addonでstubする方向に軌道修正です。
まったく問題なく動作しているので早くこれにすればよかったかな、と思う今日此の頃です。

管理人

Recent Posts

CanvaがSerif (Affinity) を買収

私は使ったことがないのですが名前はよく聞…

2週間 ago

Serifのスプリングセール – アドオンが50%オフ

Affinity Photoなどレタッチ…

1か月 ago

音声がロボットのようになるときの対処

リモート会議などでたまに相手の音声がおか…

2か月 ago

Serifのブラックフライデー – 全品40%オフ V1ユーザは更にお得!

恒例のブラックフライデーセールが始まりま…

5か月 ago

[rust] rayonで書き直してみました

前回のコードを元にrayonを使った処理…

5か月 ago

[rust] async-stdで書き直してみました

前回のコードをasync-stdで書き直…

5か月 ago