it-roy-ru.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);
    })    
  })
});

Мне уже было трудно понять, как заставить работать jest.mock, потому что переменные должны начинаться с префикса mock.

Но если я хочу проверить аргументы метода search, мне нужно сделать макетную функцию доступной в моих тестах.

Если я преобразую насмешливую часть, чтобы использовать переменную:

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

Я получаю эту ошибку:

Ошибка типа: (0, _search.search) не является функцией

Что бы я ни пытался получить доступ к jest.fn и проверить аргументы, я не могу заставить его работать.

Что я делаю неправильно?

19
ghusse

Эта проблема

Причина, по которой вы получаете эту ошибку, связана с тем, как выполняются различные операции.

Несмотря на то, что в исходном коде вы импортируете только SearchContainerпосле, присваивая значение mockSearch и вызывая jest mock, specs указывает, что: Before instantiating a module, all of the modules it requested must be available.

Следовательно, во время импорта SearchContainer и, в свою очередь, импорта search, ваша переменная mockSearch по-прежнему не определена.

Это может показаться странным, поскольку это также может означать, что search.js еще не высмеивается, и поэтому насмешка не будет работать вообще. К счастью, (babel-) jest гарантирует, что вызовы mock и аналогичные функции будут подниматься даже на выше, чем импорт, так что имитация будет работать.

Тем не менее, присвоение mockSearch, на которое ссылается функция макета, не будет поднято с вызовом mock. Итак, порядок соответствующих операций будет примерно таким:

  1. Установить фиктивную фабрику для ./search.js
  2. Импортируйте все зависимости, которые будут вызывать фиктивную фабрику для функции, чтобы дать компоненту
  3. Присвойте значение mockSearch

Когда выполняется шаг 2, функция search, переданная компоненту, будет неопределенной, и назначение на шаге 3 будет слишком поздно, чтобы это изменить.

Решение

Если вы создадите фиктивную функцию как часть вызова mock (так что она тоже будет поднята), она будет иметь действительное значение при импорте модулем компонента, как показано в вашем предыдущем примере.

Как вы указали, проблема начинается, когда вы хотите сделать макетную функцию доступной в ваших тестах. Для этого есть одно очевидное решение: отдельно импортировать модуль, который вы уже смоделировали.

Поскольку теперь вы знаете, что шутка на самом деле происходит перед импортом, тривиальный подход будет следующим:

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');

Это не должно иметь каких-либо функциональных различий, но может сделать вашу работу немного более явной (и должно помочь любому, использующему систему проверки типов, такую ​​как Flow, поэтому она не думает, что вы пытаетесь вызвать фиктивные функции). на оригинале search).

Дополнительное примечание

Все это строго необходимо, если вам нужно смоделировать экспорт по умолчанию самого модуля. В противном случае (как указывает @publicJorn) вы можете просто переназначить определенный соответствующий элемент в тестах, например, так:

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

beforeEach(() => {
  search.search = jest.fn(() => mockPromise);
});
37
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