関数の中で呼んでる関数をspyOn
するのにかなり手こずったので、やり方を残しておきます。
恐らく CommonJS だけでの話なので、TypeScript とか ES Module の人には多分関係ないと思います。
確認環境
環境 | バージョン |
---|---|
Node.js | 12.18.3 |
Jest | 26.4.2 |
やりたいこと
以下の実装のときに、parentFunc()
を呼んだ時にchildFunc()
が呼ばれることをテストしたいだけです。
functionparentFunc(){console.log('called parentFunc');childFunc('XXXX');}functionchildFunc(param){console.log(`called childFunc ${param}`);}
各ケース紹介
Case1 そもそも構文がおかしい
テストするためには関数を export する必要がありますが、愚直過ぎて構文的に実行不能になるケースです。
case1.js
exports.parentFunc=()=>{console.log('called parentFunc');// childFuncは別モジュールなので呼べないchildFunc('XXXX');};exports.childFunc=(param)=>{console.log(`called childFunc ${param}`);};
Case2 スコープ違いでテストが失敗する
この実装を実行すると期待通り動作するので、一見すると大丈夫そうに見えます。
case2.js
functionparentFunc(){console.log('called parentFunc');childFunc('XXXX');}functionchildFunc(param){console.log(`called childFunc ${param}`);}module.exports={parentFunc,childFunc};
しかしこのテストを流すと失敗します。何故でしょうか?
実はparentFunc()
が呼び出すchildFunc()
は下記のcase2
の中にいません。
これはparentFunc()
のスコープ内にchildFunc()
がいないことが原因です。
case2.spec.js
constcase2=require('./case2');// 余談ですが、こうするとjest.spyOn()の第一引数を満たせないので落ちます// const { parentFunc, childFunc } = require('./case2');describe('inside call test',function(){it('parentFunc',function(){constspy=jest.spyOn(case2,'parentFunc');case2.parentFunc();expect(spy).toHaveBeenCalled();});it('childFunc',function(){constspy=jest.spyOn(case2,'childFunc');case2.parentFunc();// childFuncはcase2に属していないため呼ばれないexpect(spy).toHaveBeenCalled();});});
Case3 テストが成功するケース
case3.js
constparentFunc=()=>{console.log('called parentFunc');// parentFunc()の中にchildオブジェクトを注入することで、// jestがchildFunc()を認識できるようにするchild.childFunc('XXXX');};constchild={childFunc:(param)=>{console.log(`called childFunc ${param}`);},};// childFuncでなく、childオブジェクトをexportするのが味噌ですmodule.exports={parentFunc,child};
このテストは成功します。
理由はソース中のコメントに書いてあるとおりです。
case3.spec.js
constcase3=require('./case3');describe('inside call test',function(){it('parentFunc',function(){constspy=jest.spyOn(case3,'parentFunc');case3.parentFunc();expect(spy).toHaveBeenCalled();});it('childFunc',function(){// 注入している側のオブジェクトを参照するconstspy=jest.spyOn(case3.child,'childFunc');case3.parentFunc();// child.childFuncはcase3に属しているため呼ばれるexpect(spy).toHaveBeenCalled();});});
余談
console.log()
やprocess.exit()
は何もしなくてもspyOn()
にかけられるので、恐らく全てのオブジェクトの中に暗黙的に注入されていそうな気がしました。(Jestがやっているのか、Node.jsがやってるのか、どっちなのかはわかりませんが…)