モジュール内で呼び出されてるメソッドをスタブ化できるproxyQuireが便利すぎた。
テストを書いて困ったことがあったので、備忘録して書いておきます。
実装側はこんな感じ。
//Goods.js テスト対象 import {readFileSync} from 'fs'; const goodThings = JSON.parse(readFileSync('/rankValues/good')); const sosoThings = JSON.parse(readFileSync('/rankValues/soso')); const badThings = JSON.parse(readFileSync('/rankValues/bad')); export default class Goods { get (status) { let goods; const goodsList = [goodThings, sosoThings, badThings]; goodsList.forEach((key) => { if (status === key) goods = goodsList[key]; }); return goods; } }
こんな感じのモジュールがあって、
無心でテストを書いてたらイケてないことに気付きました。
(途中で気付いたのですが、最後まで無心ならこうなってただろうという想定で書きました)
//BadTest.js イケてなかったテスト import 'mocha'; import assert from 'power-assert'; import {readFileSync} from 'fs'; import Goods from '../../src/Goods'; const goodThings = JSON.parse(readFileSync('/rankValues/good')); const sosoThings = JSON.parse(readFileSync('/rankValues/soso')); const badThings = JSON.parse(readFileSync('/rankValues/bad')); describe('テスト.だめな例', () => { let goods = new Goods(); }); it('Good!', () => { const result = goods.get('good'); assert.deepEqual(result, goodThings); }); it('Soso!', () => { const result = goods.get('soso'); assert.deepEqual(result, sosoThings); }); it('Bad....', () => { const result = goods.get('bad'); assert.deepEqual(result, badThings); }); });
このテストケースの問題点としては、実装されたデータを使用しているという点ですね。
テストケースのデータは定数(enumとか?)以外は
テスト用のデータを使うのがいいですよね。。
例えば
あくまでテストのデータの中身を検証するテストではなくて、get()の実装を見るテストなので・・・
ただし変更が困難なのがconstで定義されたgoodsがclassの外側に出ていること。
これはimportで読み込まれた時点で処理がスタート(この場合はfs.readFileSync()とJSON.parse())してるため
うかつに変えることができません・・・
また、今回のソースとは異なりますが
//hoge.jsのimport文 import Foo from 'foo'; // foo.jsのimport文 import Bar from 'bar';
のように複雑にimportされているテストだと、
hoge.jsを成功させるにはFooとBarの処理を想定してテストを書かなきゃいけません。
これじゃ単体テストの意味がありません・・・。
そこで必要になるのがproxyQuireです。
簡単にいえばimportなどで呼ばれたモジュールやexport以外のモジュールや
関数などをスタブ化出来るというものです。
sinon.jsと組み合わせで実装していきます。
//Test.js import 'mocha'; import assert from 'power-assert'; import proxyQuire from 'proxyquire'; import sinon from 'sinon'; import Goods from '../../src/Goods';; // この3行はテストデータです。実装側のファイルは使用しません(読まない)。 const goodThings = {good: {goods: ['tv','tablet','guitar'],}}; const sosoThings = {soso: {goods: ['fruit','meet','juice'],}}; const badThings = {bad: {goods: ['shit','paper','trash'],}}; function createProxyQuire() { // スタブ化していきます。今回スタブ化するのはfs.readFileSyncのみなので // 3種類の引数が渡されたらテストデータを返すようスタブを作成します。 const stubReadFileSync = sinon.stub(); stubReadFileSync.withArgs('/rankValues/good') .returns(JSON.stringify(goodThings)); stubReadFileSync.withArgs('/rankValues/soso') .returns(JSON.stringify(sosoThings)); stubReadFileSync.withArgs('/rankValues/bad') .returns(JSON.stringify(badThings)); // ProxyQuireを使用します。 // 第一引数にテスト対象のモジュール(相対パス), // 第二引数にスタブ化したいものをkey-value形式でかいていきます。 const proxyQuireAuthService = proxyQuire('../../src/Goods',{ fs:{ readFileSync: stubReadFileSync } }); return proxyQuireAuthService; } describe('proxyQuireを使ったテスト', () => { const Goods = createProxyQuire(); }); it('Good!', () => { const result = Goods.default.prototype.get('good'); assert.deepEqual(result, goodThings); }); it('Soso!', () => { const result = Goods.default.prototype.get('soso'); assert.deepEqual(result, sosoThings); }); it('Bad....', () => { const result = Goods.default.prototype.get('bad'); assert.deepEqual(result, badThings); }); });
本日はこんな感じです。