IMAGE: https://cdn-ak.f.st-hatena.com/images/fotolife/i/ikmbear/20210819/20210819002958.png
経緯
JavaScriptの練習として、JavaScriptを解いており、ついでにJestでテストも書いていました。
テスト自体はパスするようにできたのですが、テスト結果にも実行ファイルに書かれているconsole.log
の結果が表示されてしまう始末。これをどうにか表示させないようにするため、色々と調べてみました。
TL:DR;
Node.jsでファイルが直接実行されているかどうかは、require.main === moduleで判断することができる。参考:https://nodejs.org/api/modules.html#modules_accessing_the_main_module
__FILE__ = $PROGRAM_NAME
的なイディオムがないか探してみる
Rubyのやりたいことは次の2つです。- 実行ファイル(fizzbuzz.js
)を実行した際には、console.log
の結果を表示したい- 別のファイルから呼ばれた際には、console.log
を表示したくない
Rubyの場合、以下のように記述することで、そのファイルが直接実行されたときだけ、特定の処理を呼び出すことができます。
JavaScriptでも同じような処理ができないかと思い、「FILE JavaScript」で検索してみました。
しかし、JavaScriptの__FILE__
はファイルのJavaScriptを参照し、また$PROGRAM_NAME
に相当するものも見つからなかったため、一度この方向性は断念しました。
fjordbootcamp内で質問してみる
上記経緯からfjordboocamp内で質問してみたところ、「『Jest console.log』で$1と、console.log
をMock化しろと出ているよ」との声をいただきました。
先ほどは実行ファイルの方で表示を制御する方法で検討していましたが、テストファイルで表示を制御する方法を提示いただいたわけです。
Jestでconsole.logをMock化してみる
「Jest console.log」で検索した見つかった、下記記事を参考にMock化を図りました。
Jestでconsole.logをモックする. 最近はテストを書くときはほとんどJestを使っています。今回は、v19から導入さ… | by 赤芽 | Medium
console.logの結果が残っています。これは、mockObj.mockImplementation(() => customImplementation) かobject[methodName] = jest.fn(() => customImplementation) を使うことで上書きできます。
Jestのドキュメント(jest.spyOn)にも記載されているとおり、以下の記述でconsole
オブジェクトのlog
メソッドを別の処理(x => x
つまりなにもしない)に置き換えることができます。
しかしながら、テスト実行結果には変わらずconsole.log
の結果が出力されています。
Mock化とメソッドの上書きが正しく実行されているのか、それとも置き換え対象のメソッドがないのかの切り分けのため、以下のようにconsole.log
が呼ばれたかどうかをテストに追記します。
この結果はテスト失敗、つまりconsole.log
は呼ばれていないこととなり、テストケースで呼ばれていない処理をMock化しても意味がないことがわかりました。
console.logが何によって実行されているか確認してみる
テストケース内で呼ばれていないことがわかったため、消去法的に実行ファイルfizzbuzz.js
をrequire
した瞬間にconsole.log
が実行されていると推測できます。
(ちょっとここらへんどう検索したか忘れましたが、)実際にrequire
は実行された瞬間にそのファイルを読み込む仕様とSなっており、これを回避するための策として、以下の手順が書かれている記事をいくつか見つけました。
module.parent
はrequire元であり、自身がrequireされていれば、falsyになるという仕組みです。
module.parentの代替案を探す
いずれの記事も更新日時が古かったため、Node.jsの公式ドキュメントを確認すると、module.parent
は非推奨となっていました。
Deprecated: Please use require.main and module.children instead.
Modules: CommonJS modules | Node.js v16.7.0 Documentation
代わりにrequire.main
かmodule.children
を利用するように記載があります。
require.main
を確認すると、参考リンクに探し求めていた解決方法がありました😄
When a file is run directly from Node.js, require.main is set to its module.Modules: CommonJS modules | Node.js v16.7.0 Documentation
「Node.jsから直接ファイルが実行されると、require.main
には、自身のmodule
が設定される。」つまり、次のように記述することで要件を満たすことができたのでした。
まとめ
Node.jsでファイルが直接実行されているかどうかは、require.main === moduleで判断することができる。 参考:https://nodejs.org/api/modules.html#modules_accessing_the_main_module
今回全部調べ終わってから気がついたのですが、「JavaScirpt 直接実行された場合のみ」とか検索すると、日本語記事もいくつかヒットしましたね。しかしながら、非推奨のmodule.parent
を記載しているものもあったので、自分で調べてみてよかったです。