0
Help us understand the problem. What are the problem?

posted at

updated at

Organization

jest.spyOnでclassのインスタンスメソッドをモック化するとnot a functionエラーになる場合

問題

クラスのインスタンスメソッド内で、APIで何かをfetchしてくるケースを考えます。たとえば以下のようにgetGlobalIp()でグローバルIPを取得。

Hoge.ts
import axios from 'axios';

export class Hoge {
  getGlobalIp = async (): Promise<string> => {
    const { data } = await axios.get<{ origin: string }>('https://httpbin.org/ip');
    return data.origin;
  };
}

この戻り値が常に固定値になるように、jest.spyOnでモック化してみます。

Hoge.test.ts
import { Hoge } from './Hoge';

it('グローバルIPアドレスを取得', async () => {
  jest.spyOn(Hoge.prototype, 'getGlobalIp').mockReturnValue(Promise.resolve('8.8.8.8'));

  const hoge = new Hoge();
  expect(await hoge.getGlobalIp()).toBe('8.8.8.8');
});

すると、because it is not a functionとか叱られてエラーになっちゃいました。

 FAIL  test/Hoge.test.ts
  ✕ グローバルIPアドレスを取得

  ● グローバルIPアドレスを取得

    Cannot spy the getGlobalIp property because it is not a function; undefined given instead

      2 |
      3 | it('グローバルIPアドレスを取得', async () => {
    > 4 |   jest.spyOn(Hoge.prototype, 'getGlobalIp').mockReturnValue(Promise.resolve('8.8.8.8'));
        |        ^
      5 |
      6 |   const hoge = new Hoge();
      7 |   expect(await hoge.getGlobalIp()).toBe('8.8.8.8');

      at ModuleMocker.spyOn (../../node_modules/jest-mock/build/index.js:783:15)
      at Object.<anonymous> (test/Hoge.test.ts:4:8)

TypeScriptで書いてるので、エディタでエラーが出ないってことはメソッドはちゃんと存在するということ。
CleanShot 2022-08-05 at 07.20.47@2x.png

もしメソッド名間違えてたら、こうなります。
CleanShot 2022-08-05 at 07.20.28@2x.png

最初ぜんぜん意味がわからずドハマリしたのですが、一応回避策が見つかったので書いておきます。

対策

インスタンスメソッドの書き方が問題でした。
クラスフィールド(インスタンス変数)に関数オブジェクトを代入する記法だと、jest.spyOnでは関数として認識してくれないのです。
アロー関数でもfunctionでも、どちらもNG。

NGパターン
export class Hoge {
  getGlobalIp = async () => {};
}

export class Hoge {
  getGlobalIp = async function () {};
}

メソッド構文で書くと、ちゃんとspyOn成功します。

OKパターン
export class Hoge {
  async getGlobalIp() {}
}

修正版の全体

Hoge.ts
import axios from 'axios';

export class Hoge {
  async getGlobalIp(): Promise<string> {
    const { data } = await axios.get<{ origin: string }>('https://httpbin.org/ip');
    return data.origin;
  }
}

実行結果

 PASS  test/Hoge.test.ts
  ✓ グローバルIPアドレスを取得 (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.501 s, estimated 4 s

おわりに

thisの関係でクラスフィールド&アロー関数でメソッド定義したいことも多いから、クラスフィールドで定義したメソッドもspyOnできるようにならないもんかなぁ・・・
誰か詳しい人いたら教えて下さい!

ちなみに、クラスメソッド(static)は、クラスフィールドとしてメソッド定義してもちゃんとspyOnできました。インスタンスメソッドも、Jestのバージョンアップで対応される可能性はありそうですね。

この記事で使ったライブラリのバージョンは以下です。

  • typescript@4.7.4
  • jest@28.1.3

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
0
Help us understand the problem. What are the problem?