🍣

rewireを使ってexportされていない関数をテストする

2023/02/04に公開

こんなケースありませんか?

funcB は export しているけど、funcA は export していない。
でも、funcB の中で、funcA は呼んでいるので、テストでもちゃんと funcA が呼ばれているか確認しておきたい。

func.ts
const funcA = () => {
  return true;
};

export const funcB = () => {
  if (funcA()) { // <- funcAが呼ばれているかどうかをテストしたい
    return 'funcA return true';
  }
  return 'funcA return false';
};

どうやってテストすれば良いのか色々調べていると、rewire というライブラリを見つけました。
https://github.com/jhnns/rewire

README を見ると以下のように書いてあります。

rewire adds a special setter and getter to modules so you can modify their behaviour for better unit testing. You may

  • inject mocks for other modules or globals like process
  • inspect private variables
  • override variables within the module.

セッターとゲッターをテストしたいモジュールに対して付けることで、テストの振る舞いを変えることが出来ると。
後は、プライベートの変数の検出も出来るそう。

しかし、CommonJS にしか対応していないとの制限がありました。

The current version of rewire is only compatible with CommonJS modules.

使ってみる

$ yarn add -D rewire @types/rewire

テストする関数

func.ts
const funcA = () => {
  return true;
};

export const funcB = () => {
  if (funcA()) { // <- funcAが呼ばれているかどうかをテストしたい
    return 'funcA return true';
  }
  return 'funcA return false';
};

テスト関数

func.spec.ts
const rewire = require('rewire');
const service = rewire('../../src/libs/func.ts');

const mockFuncA = jest.fn(() => false); // falseにしてみる
service.__set__('funcA', mockFuncA);

describe('func', () => {
    test('funcAが1回呼ばれている', () => {
        service.funcB();
        expect(mockFuncA).toHaveBeenCalledTimes(1);
        expect(service.funcB()).toBe('funcA return true');
    });
});

export {};

これで実行してみると、以下エラーでそもそもテストが実行出来ませんでした。

    ...src/libs/func.ts:5
    export const funcB = () => {
    ^^^^^^

    SyntaxError: Unexpected token 'export'

      1 | export {};
      2 | const rewire = require('rewire');
    > 3 | const service = rewire('../../src/libs/func.ts');
        |                 ^
      4 |
      5 | const mockFuncA = jest.fn(() => false);
      6 | service.__set__('funcA', mockFuncA);

rewireCommonJS にしか対応していないためです。
これを実行できるようにするためには、実行の仕方を変える必要がありました。
このissueのコメントでも言及されています。

package.json
{
    "scripts": {
-       "test": "yarn jest",
+       "test": "ts-node -O '{\"module\":\"commonjs\"}' node_modules/jest/bin/jest.js",
    }
}
$ yarn add -D ts-node

改めて実行します。
今度はしっかりテストをしてくれました。

実行されているか分かり易いようにあえて落としていますが、
false を返すようにモックしたので、ちゃんと効いているようです。

    expect(received).toBe(expected) // Object.is equality

    Expected: "funcA return true"
    Received: "funcA return false"

      10 |     service.funcB();
      11 |     expect(mockFuncA).toHaveBeenCalledTimes(1);
    > 12 |     expect(service.funcB()).toBe('funcA return true');
         |                             ^
      13 |   });
      14 | });
      15 |

      at Object.<anonymous> (test/libs/func.spec.ts:12:29)
GitHubで編集を提案

Discussion