モジュールがモックされていないときに、インポートされた名前付き関数をJestでモックする方法


111

Jestでテストしようとしている次のモジュールがあります。

// myModule.js

export function otherFn() {
  console.log('do something');
}

export function testFn() {
  otherFn();

  // do other things
}

上に示したように、いくつかの名前付き関数をエクスポートし、重要なことにをtestFn使用しotherFnます。

Jestで、の単体テストを作成しているときに、の単体テストにエラーが影響しないようにするためtestFnotherFn関数をモックしたいと思います。私の問題は、それを行うための最良の方法がわからないということです。otherFntestFn

// myModule.test.js
jest.unmock('myModule');

import { testFn, otherFn } from 'myModule';

describe('test category', () => {
  it('tests something about testFn', () => {
    // I want to mock "otherFn" here but can't reassign
    // a.k.a. can't do otherFn = jest.fn()
  });
});

どんな助け/洞察も大歓迎です。


7
私はこれをしません。とにかく、モックは一般的にやりたいことではありません。また、(サーバー呼び出しなどを行うために)何かをモックする必要がある場合はotherFn、別のモジュールに抽出してモックする必要があります。
kentcdodds 2016

2
@jrubinsが使用するのと同じアプローチでテストしています。function A呼び出し元の動作をテストしますfunction Bが、実際の実装を実行したくないのfunction Bは、実装されたロジックをテストしたいだけだからですfunction A
jplaza 2016年

44
@ kentcdodds、「モックは一般的にあなたがやりたいことではない」とはどういう意味か明確にできますか?モックは確かによく使用されるものであり、おそらく(少なくともいくつかの)正当な理由があるため、これはかなり広い(過度に広い?)ステートメントのようです。それで、あなたはおそらくここでモックが良くないかもしれない理由について言及していますか、それともあなたは本当に一般的な意味ですか?
Andrew Willems 2016

2
多くの場合、モックは実装の詳細をテストしています。特にこのレベルでは、テストが機能するという事実(コードが機能するということではない)よりも実際には検証されていないテストにつながります。
kentcdodds 2016

3
記録のために、何年も前にこの質問を書いたので、私はそれ以来、私がどれだけのモックをしたいかについて私の調子を変えました(そしてもうこのようなモックをしません)。最近、私は@kentcdoddsと彼のテスト哲学に非常に同意します(そして彼のブログと@testing-library/reactそこにあるすべてのReacterを強くお勧めします)が、これは論争の的となる主題であることを私は知っています。
JonRubins19年

回答:


100

jest.requireActual()内部で使用jest.mock()

jest.requireActual(moduleName)

モジュールがモック実装を受け取る必要があるかどうかに関するすべてのチェックをバイパスして、モックではなく実際のモジュールを返します。

私はあなたが必要とし、返されたオブジェクト内に広がるこの簡潔な使用法を好みます:

// myModule.test.js

jest.mock('./myModule.js', () => (
  {
    ...(jest.requireActual('./myModule.js')),
    otherFn: jest.fn()
  }
))

import { otherFn } from './myModule.js'

describe('test category', () => {
  it('tests something about otherFn', () => {
    otherFn.mockReturnValue('foo')
    expect(otherFn()).toBe('foo')
  })
})

このメソッドは、Jestのマニュアルモックのドキュメント(の終わり近く)でも参照されています。

手動モックとその実際の実装の同期を維持するには、jest.requireActual(moduleName)エクスポートする前に、手動モックで使用し、モック関数で修正する実際のモジュールを要求すると便利な場合があります。


4
Fwiw、returnステートメントを削除し、矢印関数の本体を括弧で囲むことで、これをさらに簡潔にすることができます。jest.mock('./myModule', () => ({ ...jest.requireActual('./myModule'), otherFn: () => {}}))
ニック

2
これはうまくいきました!これは受け入れられた答えでなければなりません。
JRJurman

2
...jest.requireActualbabelを使用したパスエイリアシングがあるため、正しく機能しませんでした。パスからエイリアシングを...require.requireActual削除した場合、または削除した後に機能します
TzahiLeh19年

1
otherFunこの場合に呼び出されたものをどのようにテストしますか?仮定otherFn: jest.fn()
Stevula

1
@Stevula実際の使用例を示すために回答を更新しました。私は表示されmockReturnValue、より良い嘲笑バージョンは、オリジナルの代わりに呼ばなっていることを実証するための方法を、しかし、あなたは本当にただそれは、戻り値に対してアサートせずに呼び出されているかどうかを確認したい場合は、冗談マッチャーを使用することができます.toHaveBeenCalled()
gfullam

36
import m from '../myModule';

私には機能しません、私は使用しました:

import * as m from '../myModule';

m.otherFn = jest.fn();

5
他のtestSに干渉しないように、テスト後にotherFnの元の機能をどのように復元しますか?
aequitas 2017年

1
すべてのテストの後にモックをクリアするようにjestを構成できると思いますか?ドキュメントから:「clearMocks構成オプションを使用して、テスト間でモックを自動的にクリアできます。」clearkMocks: truejest package.jsonconfigを設定できます。facebook.github.io/jest/docs/en/mock-function-api.html
Cole

2
これがグローバル状態を変更するような問題である場合は、いつでも元の機能をある種のテスト変数内に保存し、テスト後に戻すことができます
bobu 2018年

1
constオリジナル; beforeAll(()=> {original = m.otherFn; m.otherFn = jest.fn();})afterAll(()=> {m.otherFn = original;})動作するはずですが、テストしませんでしたそれ
bobu 2018年

どうもありがとうございます!これで私の問題は解決しました。
スロボダンKrasavčević

25

私はこのパーティーに遅れているようですが、はい、これは可能です。

testFnotherFn モジュールを使用して呼び出す必要があります

もしtestFn使用するモジュールを呼び出すotherFn、その後のためのモジュールのエクスポートをotherFn嘲笑することができ、testFnモックを呼び出します。


これが実際の例です:

myModule.js

import * as myModule from './myModule';  // import myModule into itself

export function otherFn() {
  return 'original value';
}

export function testFn() {
  const result = myModule.otherFn();  // call otherFn using the module

  // do other things

  return result;
}

myModule.test.js

import * as myModule from './myModule';

describe('test category', () => {
  it('tests something about testFn', () => {
    const mock = jest.spyOn(myModule, 'otherFn');  // spy on otherFn
    mock.mockReturnValue('mocked value');  // mock the return value

    expect(myModule.testFn()).toBe('mocked value');  // SUCCESS

    mock.mockRestore();  // restore otherFn
  });
});

2
これは基本的に、Facebookで使用され、この投稿の途中でFacebook開発者によって説明されているアプローチのES6バージョンです。
ブライアンアダムス

myModuleをそれ自体にインポートする代わりに、exports.otherFn()
andrhamm 2018年

3
@andrhammexportsはES6には存在しません。exports.otherFn()ES6は以前のモジュール構文にコンパイルされているため、呼び出しは現在機能しますが、ES6がネイティブでサポートされている場合は機能しなくなります。
ブライアンアダムス

今まさにこの問題を抱えており、私は以前にこの問題に遭遇したことがあると確信しています。ツリーの揺れを助けるために、大量のexports。<methodname>を削除する必要があり、多くのテストに失敗しました。これが効果があるかどうかはわかりますが、とてもハッキーなようです。私はこの問題に何度も遭遇し、他の回答が言っているように、babel-plugin- rewireまたはさらに良いnpmjs.com/package/rewiremockのようなものは、上記もできるとかなり確信しています。
Astridax 2018年

戻り値をモックする代わりに、スローをモックすることは可能ですか?編集:できます、これがどのようにstackoverflow.com/a/50656680/2548010
ビッグマネー

10

トランスパイルされたコードは、babelotherFn()が参照しているバインディングを取得することを許可しません。関数expessionを使用すると、モックを実現できるはずotherFn()です。

// myModule.js
exports.otherFn = () => {
  console.log('do something');
}

exports.testFn = () => {
  exports.otherFn();

  // do other things
}

 

// myModule.test.js
import m from '../myModule';

m.otherFn = jest.fn();

しかし、前のコメントで@kentcdoddsが述べたように、おそらくモックを作成したくないでしょうotherFn()。むしろ、新しい仕様を書いて、otherFn()必要な呼び出しをモックするだけです。

したがって、たとえば、otherFn()httpリクエストを行っている場合...

// myModule.js
exports.otherFn = () => {
  http.get('http://some-api.com', (res) => {
    // handle stuff
  });
};

ここでは、モックhttp.getされた実装に基づいてアサーションをモックして更新する必要があります。

// myModule.test.js
jest.mock('http', () => ({
  get: jest.fn(() => {
    console.log('test');
  }),
}));

1
otherFnとtestFnが他のいくつかのモジュールで使用されている場合はどうなりますか?これらの2つのモジュールを使用する(ただしスタックの深さはある)すべてのテストファイルにhttpモックを設定する必要がありますか?また、testFnのテストがすでにある場合は、testFnを使用するモジュールでhttpではなくtestFnを直接スタブ化してみませんか?
2017年

1
したがって、otherFnが壊れている場合、それに依存するすべてのテストに失敗します。また、otherFn内部に5つのifがある場合は、それをテストする必要があるかもしれません。testFn、これらすべてのサブケースで正常に機能いる。これで、テストするコードパスがさらに多くなります。
totty.js 2018年

6

私はこれがずっと前に尋ねられたことを知っています、しかし私はちょうどこのまさに状況に遭遇し、そして最終的にうまくいく解決策を見つけました。だからここでシェアしたいと思いました。

モジュールの場合:

// myModule.js

export function otherFn() {
  console.log('do something');
}

export function testFn() {
  otherFn();

  // do other things
}

次のように変更できます。

// myModule.js

export const otherFn = () => {
  console.log('do something');
}

export const testFn = () => {
  otherFn();

  // do other things
}

関数ではなく定数としてエクスポートします。この問題はJavaScriptでの巻き上げに関係していると思いますが、使用constするとその動作が妨げられます。

次に、テストで次のようなものを使用できます。

import * as myModule from 'myModule';


describe('...', () => {
  jest.spyOn(myModule, 'otherFn').mockReturnValue('what ever you want to return');

  // or

  myModule.otherFn = jest.fn(() => {
    // your mock implementation
  });
});

これで、モックは通常の期待どおりに機能するはずです。


2

私はここで見つけた答えを組み合わせて問題を解決しました:

myModule.js

import * as myModule from './myModule';  // import myModule into itself

export function otherFn() {
  return 'original value';
}

export function testFn() {
  const result = myModule.otherFn();  // call otherFn using the module

  // do other things

  return result;
}

myModule.test.js

import * as myModule from './myModule';

describe('test category', () => {
  let otherFnOrig;

  beforeAll(() => {
    otherFnOrig = myModule.otherFn;
    myModule.otherFn = jest.fn();
  });

  afterAll(() => {
    myModule.otherFn = otherFnOrig;
  });

  it('tests something about testFn', () => {
    // using mock to make the tests
  });
});

0

ここでの最初の答えに加えて、babel-plugin-rewireを使用して、インポートされた名前付き関数をモックすることもできます。名前付き関数の再配線については、表面的にセクションを確認でき ます。

ここでの状況の直接的な利点の1つは、関数から他の関数​​を呼び出す方法を変更する必要がないことです。


node.jsで動作するようにbabel-plugin-rewireを構成する方法は?
ティムールギラウリ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.