Jestを使用してES6モジュールのインポートをモックするにはどうすればよいですか?


281

これは不可能だと思い始めていますが、とにかく聞きたいです。

ES6モジュールの1つが特定の方法で別のES6モジュールを呼び出すことをテストしたいと思います。ジャスミンを使えば、これはとても簡単です-

アプリコード:

// myModule.js
import dependency from './dependency';

export default (x) => {
  dependency.doSomething(x * 2);
}

そしてテストコード:

//myModule-test.js
import myModule from '../myModule';
import dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    spyOn(dependency, 'doSomething');

    myModule(2);

    expect(dependency.doSomething).toHaveBeenCalledWith(4);
  });
});

Jestと同等のものは何ですか?これはやりたいことのように簡単なことだと思いますが、それを理解しようとして髪を切り裂いてきました。

一番近いのは、importsをrequiresに置き換え、それらをテスト/関数内に移動することです。どちらも私がやりたいことではありません。

// myModule.js
export default (x) => {
  const dependency = require('./dependency'); // yuck
  dependency.doSomething(x * 2);
}

//myModule-test.js
describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    jest.mock('../dependency');

    myModule(2);

    const dependency = require('../dependency'); // also yuck
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

ボーナスポイントとして、内部の関数dependency.jsがデフォルトのエクスポートである場合は、すべてを機能させたいと思います。ただし、私はデフォルトのエクスポートをスパイすることはJasmineでは機能しないことを知っています(または、少なくともそれを機能させることはできませんでした)。


とにかく、私はこのプロジェクトにBabelを使用しているので、今のところimportsをrequires にトランスパイルし続けてもかまいません。頭を上げてくれてありがとう。
Cam Jackson

私はTSクラスAを持っており、それはいくつかの機能は、我々はクラスAがクラスB機能のdoSomething()の嘲笑のバージョンに呼び出しを行うように模擬することができますどのようにdoSomething()クラスBのを言うことができます呼び出す場合
カイラスyogeshwar

この問題を発見したい人のためにもっとgithub.com/facebook/jest/issues/936
omeralper

回答:


219

私はを含むハックを使用してこれを解決することができましたimport *。名前付きのエクスポートとデフォルトのエクスポートの両方で機能します!

名前付きエクスポートの場合:

// dependency.js
export const doSomething = (y) => console.log(y)

// myModule.js
import { doSomething } from './dependency';

export default (x) => {
  doSomething(x * 2);
}

// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.doSomething = jest.fn(); // Mutate the named export

    myModule(2);

    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

またはデフォルトのエクスポートの場合:

// dependency.js
export default (y) => console.log(y)

// myModule.js
import dependency from './dependency'; // Note lack of curlies

export default (x) => {
  dependency(x * 2);
}

// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.default = jest.fn(); // Mutate the default export

    myModule(2);

    expect(dependency.default).toBeCalledWith(4); // Assert against the default
  });
});

Mihai Damianが以下でかなり正しく指摘しているように、これはのモジュールオブジェクトを変更dependencyしているため、他のテストに「リーク」します。したがって、このアプローチを使用する場合は、元の値を保存し、各テストの後に再び設定する必要があります。Jestでこれを簡単に行うには、元の値を簡単に復元できるため、代わりにspyOn()メソッドを使用してjest.fn()、前述の「リーク」を回避します。


共有いただきありがとうございます。私は、最終結果はこれに似ていると思う-しかし、これはきれいかもしれない- stackoverflow.com/a/38414160/1882064
arcseldon

64
これは機能しますが、おそらく良い方法ではありません。テストの範囲外のオブジェクトへの変更は、テスト間で保持されるようです。これにより、後で他のテストで予期しない結果が生じる可能性があります。
ミハイダミアン

10
jest.fn()を使用する代わりに、jest.spyOn()を使用して、後で元のメソッドを復元できるため、他のテストに影響を与えません。私はここでさまざまなアプローチ(jest.fn、jest.mock、jest.spyOn)に関する素晴らしい記事を見つけました:medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c
Martinsos

2
注意:dependencyがと同じファイルに存在する場合、機能しmyModuleません。
Lu Tran、

2
Typescriptでは、変更しているオブジェクトが読み取り専用であるため、これは機能しないと思います。
adredx

171

あなたはモジュールをモックして自分でスパイを設定する必要があります:

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency', () => ({
  doSomething: jest.fn()
}))

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

4
これは正しくないようです。私は得ます:babel-plugin-jest-hoist: The second argument of jest.mock must be a function.それでコードはコンパイルさえしていません。
Cam Jackson

3
コードを更新しました。また、パスjest.mockはテストファイルに対して相対的であることに注意してください。
AndreasKöberle16年

1
これは私にとってはうまくいきましたが、デフォルトのエクスポートを使用するときはそうではありませんでした。
Iris Schaffer 2017

4
@IrisSchafferをデフォルトのエクスポートで機能さ__esModule: trueせるには、モックオブジェクトに追加する必要があります。これは、トランスパイルされたコードがトランスパイルされたes6モジュールであるかcommonjsモジュールであるかを決定するために使用される内部フラグです。
ヨハネスランペ2017年

24
デフォルトのエクスポートのjest.mock('../dependency', () => ({ default: jest.fn() }))
モック

50

jestを使用してES6依存モジュールのデフォルトのエクスポートをモックするには:

import myModule from '../myModule';
import dependency from '../dependency';

jest.mock('../dependency');

// If necessary, you can place a mock implementation like this:
dependency.mockImplementation(() => 42);

describe('myModule', () => {
  it('calls the dependency once with double the input', () => {
    myModule(2);

    expect(dependency).toHaveBeenCalledTimes(1);
    expect(dependency).toHaveBeenCalledWith(4);
  });
});

他のオプションは私の場合うまくいきませんでした。


6
1つのテストだけを作成したい場合、これをクリーンアップする最良の方法は何ですか?内部それぞれ `` `` afterEach(()=> {jest.unmock(../ dependency ');}) `` ``
nxmohamad

1
@falsarellaはその場合実際にdoMockは機能しますか?私は非常に類似した問題を抱えており、特定のテスト内でjest.doMockを試行しても何も起こりません。モジュール全体のjest.mockが正しく機能しています
Progress1ve

1
@ Progress1ve mockImplementationOnceでjest.mockを使用して試すこともできます
falsarella

1
うん、それは有効な提案ですが、テストは最初のものである必要があり、私はそのような方法でテストを書くのが好きではありません。外部モジュールをインポートし、特定の関数でspyOnを使用することで、これらの問題を回避しました。
Progress1ve

1
@ Progress1veうーん、mockImplementationOnceを各特定のテスト内に配置するつもりでした...とにかく、あなたが解決策を見つけてうれしいです:)
falsarella

38

アンドレアスの回答にさらに追加します。ES6コードでも同じ問題がありましたが、インポートを変更したくありませんでした。それはハッキーに見えました。だから私はこれをやった

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
  });
});

「dependency.js」と並行して「__mocks__」フォルダにdependency.jsを追加しました。これでうまくいきました。また、これにより、モック実装から適切なデータを返すオプションが与えられました。モックしたいモジュールへの正しいパスを指定してください。


これをありがとう。試してみます。あまりにもこのソリューションを気に入った点- stackoverflow.com/a/38414160/1882064
arcseldon

このアプローチについて私が気に入っているのは、特定のモジュールをモックしたいすべての場合に1つの手動モックを提供できる可能性があることです。たとえば、私は多くの場所で使用されている翻訳ヘルパーを持っています。__mocks__/translations.jsファイルには、単に輸出をデフォルトjest.fn()のようなもので:export default jest.fn((id) => id)
アイリスシェーファー

を使用jest.genMockFromModuleして、モジュールからモックを生成することもできます。facebook.github.io/jest/docs/...
Varunkumar Nagarajan

2
注意すべきことの1つは、ES6モジュールがモックexport default jest.genMockFromModule('../dependency')されてdependency.default、 `jest.mock( '.. dependency')を呼び出した後にすべての関数が割り当てられることですが、それ以外の場合は期待どおりに動作します。
jhk 2017

7
テストアサーションはどのように見えますか?それは答えの重要な部分のようです。expect(???)

14

2020年までの早送りで、このリンクが解決策であることがわかりました。ES6モジュール構文のみを使用https://remarkablemark.org/blog/2018/06/28/jest-mock-default-named-export/

// esModule.js
export default 'defaultExport';
export const namedExport = () => {};

// esModule.test.js
jest.mock('./esModule', () => ({
  __esModule: true, // this property makes it work
  default: 'mockedDefaultExport',
  namedExport: jest.fn(),
}));

import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function

また、知っておく必要があることの1つは、テスト中にjest.mock()を呼び出せないことです。モジュールのトップレベルで呼び出す必要があります。ただし、テストごとに異なるモックを設定する場合は、個々のテスト内でmockImplementation()を呼び出すことができます。


5

質問はすでに回答されていますが、次のように解決できます。

dependency.js

const doSomething = (x) => x
export default doSomething;

myModule.js:

import doSomething from "./dependency";

export default (x) => doSomething(x * 2);

myModule.spec.js:

jest.mock('../dependency');
import doSomething from "../dependency";
import myModule from "../myModule";

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    doSomething.mockImplementation((x) => x * 10)

    myModule(2);

    expect(doSomething).toHaveBeenCalledWith(4);
    console.log(myModule(2)) // 40
  });
});

しかし、 "require"はCommonJS構文です-OPはES6モジュールについて尋ねていました
Andy

@Andyコメントをありがとう、私は私の答えを更新しました。ところでロジックで同じこと。
スリム

2

私はこれを別の方法で解決しました。あなたがdependency.jsを持っているとしましょう

export const myFunction = () => { }

そのほかに、次の内容でdepdency.mock.jsファイルを作成します。

export const mockFunction = jest.fn();

jest.mock('dependency.js', () => ({ myFunction: mockFunction }));

テストでは、使用する依存関係のあるファイルをインポートする前に:

import { mockFunction } from 'dependency.mock'
import functionThatCallsDep from './tested-code'

it('my test', () => {
    mockFunction.returnValue(false);

    functionThatCallsDep();

    expect(mockFunction).toHaveBeenCalled();

})
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.