クックブック for 単体テスト stub/spy/mock編
はじめに
このドキュメントは、単体テストを書くときに「なんでspyだと戻り値をmockできないんだよ!?」とか「あれこのライブラリのメソッドどうやってmockすればいいの?」とか「spyとmockとstubの使い分けが全然わからん・・・」とかいう悩みを幾度となく繰り返した末にその場しのぎのテストコードを書き続けた結果、にっちもさっちも行かなくなりテストが書きづらくなってしまったコードをメンテするツラミにぶちあったっている自分のために、「次はここをみてちゃんとテスト書くんだ・・・!」という決意をするためのドキュメントです。
要はカンペ
あ、ちなみに一応調べながら書いていますが、深くはほっていないので勘違いがあるかも・・・気づいたところがあれば編集リクエストくれると嬉しいです。
対象環境
モックライブラリにjest/sinon
テストランナーはjest
NodeJsで開発したアプリケーションに対する単体テストを想定しています。
常に書き足していくことを想定しているため、バージョンはその時のstable最新を想定しています。
2019/05/17現在はjest 24.8 Sinon.Jsは7.3.2で動かしてます。
sinon
spy
spyしたメソッドが何回実行されて、戻り値は何でなんの引数が渡されたかなど、様々情報を記憶する。
spyしたメソッドは基本的にそのまま実行される
また自分が調べた限りspyしたオブジェクトのメソッドの戻り値をモックする方法が見つからなかった。
stub
Q.stubってどういうときに使うの?
A. テストでメソッドの挙動をコントロールしたいときに使うよ。
例えばエラーハンドリングとかテストしたいときとか
後XMLHttpRequest
とかreadFile
とかテスト時に実行してほしくないメソッドを実行させないために使うときもあるね。
つまりテスト内部のプログラムの挙動をコントロールしたいときに使うのが良さそう。
sinon.assert.calledWith(stbObj, arg1, arg2,..)
とかを利用すると渡された引数のテストとかもできる。
mock
また今度
jest
また今度
ユースケース
fs系のライブラリのメソッドをモックしたい
readFileSync
とかそういうやつ。まぁありがち
そんな簡単やろ!readFileSyncをモックするだけやんけ!
でも念の為に(?)console.logでモックした中身見たろ!
const fs = require('fs');
const main = () => {
const res = fs.readFileSync('index.js');
console.log(res)
return res;
}
module.exports.main = main;
const sinon = require('sinon'),
fs = require('fs'),
assert = require('assert'),
index = require('../index');
describe('test index.js', () => {
let stubFs;
beforeEach(() => {
stubFs = sinon.stub(fs, 'readFileSync').callsFake(() => '')
});
it('execute console.log', () => {
assert.equal(index.main(), '');
})
});
テスト結果
FAIL test/index.spec.js
test index.js
✕ execute console.log (8ms)
● test index.js › execute console.log
TypeError: Cannot read property 'charCodeAt' of undefined
at _callsites (node_modules/@jest/source-map/build/getCallsite.js:19:39)
at _default (node_modules/@jest/source-map/build/getCallsite.js:85:21)
at Function.write (node_modules/@jest/console/build/BufferedConsole.js:104:51)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 2.055s
Ran all test suites matching /test\/index.spec.js/i.
上記のスタブの仕方だと、すべてのreadFileSync
の戻り値をmocks
にしてしまっている。
結構ありがちでconsole.logだけでなくconfigライブラリを使用している場合とかもreadFileSync
をstubすると影響がでるっぽい。
stub.withArgs(arg1[, arg2, ...]);
Stubs the method only for the provided arguments.
与えられた引数だけメソッドをスタブするって書いてある?
というとこれで引数を指定すると、特定の引数を与えられた関数だけスタブするとかそんな素晴らしいことができる。 -> できなかった。
指定した引数以外だと値が帰ってこない。(ドキュメントにもそう書いてある)
stub.callThrough();
Causes the original method wrapped into the stub to be called when none of the conditional stubs are matched.
大変それっぽい。これを使用してテストを修正してみた。
const sinon = require('sinon'),
fs = require('fs'),
assert = require('assert'),
index = require('../index');
describe('test index.js', () => {
let stubFs;
beforeEach(() => {
stubFs = sinon.stub(fs, 'readFileSync').withArgs('index.js').callsFake(() => '')
fs.readFileSync.callThrough();
});
it('execute console.log', () => {
assert.equal(index.main(), '');
})
});
これでindex.js以外の引数がreadFileSync
に渡されたときは通常のメソッドが呼ばれるようになった。やったね。
classのメソッドをスタブしたい
sinon.stub(Class.prototype, 'method')
で行けるぞ
Author And Source
この問題について(クックブック for 単体テスト stub/spy/mock編), 我々は、より多くの情報をここで見つけました https://qiita.com/saburou_itijiku/items/7ee9b83c22bb7402624c著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .