ユニットテスト、書いてあると安心感がありますよね。
(いや、書いてないと怒られる時代に突入してるか)
しかしユニットテストって言語によって書き方の作法が違うし、テストフレームワークも複数あったりと・・・。
慣れるまでは、テスト対象のコードを実装するよりもテストコードを書く方が大変だったりします。
(テストパターンの数の問題ではなく)
また言語によっては、private なメソッドや関数のテストが面倒だったり。
今回は TypeScript で export されていない外部ファイルの function のテストの方法について紹介します。
TypeScriptのテストフレームワーク
ユニットテストを実装する際、どのようなライブラリを使うのがベターなのか。
その時代によってトレンドは様々でしょうが、やはり使い慣れたものがあった方がいいですよね。
Java や Kotlin だと、SpringBootTest や KotlinTest を使う機会が多いでしょうか。
じゃあ TypeScript は?っというと、どうやら Jest が人気のようですね。
シンプルにテストが書けて、モックの定義もしやすいとベストなのですが果たして・・・。
関数のテストコード(function)
function のテストは簡単。
test() の引数に関数名とテスト実装を定義するだけです。
1 2 3 | test('getHoge', () => { // ファンクションのテスト }); |
では、シンプルなテストを実装してみます。
hoge.ts に定義されている getHogeType() の function をターゲットとしましょう。
hoge.ts を t、テストケースのターゲットを tt とするのが一般的なのでしょうか。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import * as t from "./hoge"; test('getHogeType', () => { [ { value: "value1", result: t.Hoge.Type1 }, { value: "value2", result: t.Hoge.Type2 }, { value: "", result: undefined }, { value: undefined, result: undefined } ].forEach((tt): void => { expect(t.getHogeType(tt.value)).toBe(tt.result) }); }) |
クラスのテストコード(class)
クラスの場合は、describe() にクラス名を定義して、その中にメソッドのテストコードを書いていきます。
以下は Hoge クラスのコンストラクタと getHoge メソッドのテスト例です。
1 2 3 4 5 6 7 8 9 | describe("Hoge", () => { test("constructor", () => { // コンストラクタのテスト }); test("getHoge", () => { // メソッドのテスト }); }) |
privateのメソッドのモック化
残念ながら private な関数やメソッドのテストには素直にはいきません。
ただし、class 内の private なメソッドのモック化は簡単。
クラスのインスタンスを作成したら、[ ] の括弧でメソッド名を指定すれば、中身を書き換えることができるのです。
const hoge = new t.Hoge();
hoge[“_fuga”] = () => { return “fuga”; }
privateの関数の参照
では class 外の export されていない関数はどうすればいいか。
Java や Kotlin の時にリフレクションを使ったのと同じような状況ですね。
TypeScript の場合は rewire というライブラリを使うのが良さそう。
開発時しか使わないので、–save-dev でいいでしょう。
$ npm install –save-dev rewire @types/rewire
これでインポートしたモジュールの関数が参照できると思ったら・・・
どうやら ts ファイルではなく、ビルドして生成された js ファイルを指定しないといけないのですね。
ここで大きくハマりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import rewire from "rewire" import * as t from "./hoge" const hogeModule = rewire("./dist/hoge.js") test('_fuga', () => { [ { value: "value1", result: t.Fuga.Type1 } ].forEach((tt): void => { expect(hogeModule.__get__('_fuga')().toBe(tt.result) }) }); |
最初は ts ファイルを指定したり、拡張子を付けたり外したりと試していましたが、下記のエラーは変わらず。
まさか生成された JS ファイルの方を指定するとは思ってもなく・・・。
この手の問題で時間を潰すのはもったいないですが、これも勉強ですね。
1 2 3 4 5 6 7 8 9 | Test suite failed to run Cannot find module './hoge' Require stack: - /hoge.test.ts 10 | import * as t from "./hoge" 11 | > 12 | const hogeModule = rewire("./hoge") |
まとめ
TypeScript で export していない外部ファイルの function のテストをする方法を紹介してきました。
最初に調べていた時は、「package.json に以下を定義しろ」という記事ばかりヒットして謎だったのですよね。
{“type”: “module”}
まだまだ TypeScript や NodeJS 関連の検索には慣れていないので、もう少し理解を深めていきたいところです。