web-dev-qa-db-ja.com

jestでモジュールをモックできず、関数呼び出しをテストする

create-app-component を使用してプロジェクトを作成します。ビルドスクリプト(babel、webpack、jest)を使用して新しいアプリを構成します。

私がテストしようとしているReactコンポーネント。コンポーネントは関数を公開する別のJavaScriptファイルを必要としています。

私のsearch.jsファイル

export {
  search,
}

function search(){
  // does things
  return Promise.resolve('foo')
}

私の反応コンポーネント:

import React from 'react'
import { search } from './search.js'
import SearchResults from './SearchResults'

export default SearchContainer {
  constructor(){
    this.state = {
      query: "hello world"
    }
  }

  componentDidMount(){
    search(this.state.query)
      .then(result => { this.setState({ result, })})
  }

  render() {
    return <SearchResults 
            result={this.state.result}
            />
  }
}

単体テストでは、メソッドsearchが正しい引数で呼び出されたことを確認します。

私のテストは次のようになります。

import React from 'react';
import { shallow } from 'enzyme';
import should from 'should/as-function';

import SearchResults from './SearchResults';

let mockPromise;

jest.mock('./search.js', () => {
  return { search: jest.fn(() => mockPromise)};
});

import SearchContainer from './SearchContainer';

describe('<SearchContainer />', () => {
  it('should call the search module', () => {
    const result = { foo: 'bar' }
    mockPromise = Promise.resolve(result);
    const wrapper = shallow(<SearchContainer />);

    wrapper.instance().componentDidMount();

    mockPromise.then(() => {
      const searchResults = wrapper.find(SearchResults).first();
      should(searchResults.prop('result')).equal(result);
    })    
  })
});

変数の前にmockを付ける必要があるため、jest.mockを機能させる方法を見つけるのに苦労しました。

ただし、メソッドsearchの引数をテストする場合は、テストでモック関数を使用可能にする必要があります。

モック部分を変換して、変数を使用する場合:

const mockSearch = jest.fn(() => mockPromise)
jest.mock('./search.js', () => {
  return { search: mockSearch};
});

私はこのエラーを受け取ります:

TypeError:(0、_search.search)は関数ではありません

jest.fnにアクセスして引数をテストしようとしても、機能させることはできません。

何が間違っていますか?

23
ghusse

問題

このエラーが発生する理由は、さまざまな操作がホイストされる方法に関係しています。

元のコードでは、SearchContainerafterのみをインポートし、mockSearchに値を割り当て、jestのmockspecs は次のことを示します:Before instantiating a module, all of the modules it requested must be available

したがって、SearchContainerがインポートされた時点で、searchをインポートしても、mockSearch変数は未定義のままです。

search.jsがまだモックされていないことを暗示しているように見えるため、この奇妙なことに気付くかもしれません。したがって、モックはまったく機能しません。幸いなことに、(babel-)jestはmockおよび同様の関数への呼び出しをインポートよりも高くhigherするので、モックは作業。

それにもかかわらず、モックの関数によって参照されるmockSearchの割り当ては、mock呼び出しで引き上げられません。したがって、関連する操作の順序は次のようになります。

  1. ./search.jsの模擬ファクトリーを設定します
  2. すべての依存関係をインポートします。これにより、関数のモックファクトリが呼び出され、コンポーネントが提供されます。
  3. mockSearchに値を割り当てます

ステップ2が発生すると、コンポーネントに渡されるsearch関数は未定義になり、ステップ3での割り当てはそれを変更するには遅すぎます。

解決

mock呼び出しの一部としてモック関数を作成すると(ホイストされるように)、初期の例で示したように、コンポーネントモジュールによってインポートされたときに有効な値になります。

先ほど指摘したように、テストでモックされた関数を使用可能にしたいときに問題が始まります。これには明らかな解決策が1つあります。既にモックしたモジュールを個別にインポートします。

インポートの前に冗談のモックが実際に行われることがわかっているので、簡単なアプローチは次のとおりです。

import { search } from './search.js'; // This will actually be the mock

jest.mock('./search.js', () => {
  return { search: jest.fn(() => mockPromise) };
});

[...]

beforeEach(() => {
  search.mockClear();
});

it('should call the search module', () => {
  [...]

  expect(search.mock.calls.length).toBe(1);
  expect(search.mock.calls[0]).toEqual(expectedArgs);
});

実際、次のものを置き換えることができます。

import { search } from './search.js';

と:

const { search } = require.requireMock('./search.js');

これは機能的な違いをもたらすべきではありませんが、あなたがやっていることをもう少し明示的にするかもしれません(そしてフローなどの型チェックシステムを使用している人を助けるはずですので、モック関数を呼び出そうとしているとは思わない元のsearch)。

追記

これらすべては、モックする必要があるのがモジュール自体のデフォルトのエクスポートである場合にのみ厳密に必要です。それ以外の場合(@publicJornが指摘しているように)、テストで特定の関連メンバーを次のように再割り当てできます。

import * as search from './search.js';

beforeEach(() => {
  search.search = jest.fn(() => mockPromise);
});
45
Tomty

応答でapi呼び出しをモックするときは、async()=>テストを忘れずに、ラッパーの更新を待ってください。私のページは、典型的なcomponentDidMount => API呼び出しを行いました.

...otherImports etc...
const SomeApi = require.requireMock('../../api/someApi.js');

jest.mock('../../api/someApi.js', () => {
    return {
        GetSomeData: jest.fn()
    };
});

beforeEach(() => {
    // Clear any calls to the mocks.
    SomeApi.GetSomeData.mockClear();
});

it("renders valid token", async () => {
        const responseValue = {data:{ 
            tokenIsValid:true}};
        SomeApi.GetSomeData.mockResolvedValue(responseValue);
        let wrapper = shallow(<MyPage {...props} />);
        expect(wrapper).not.toBeNull();
        await wrapper.update();
        const state = wrapper.instance().state;
        expect(state.tokenIsValid).toBe(true);

    });
0
swandog