Как проверить асинхронную функцию с помощью spyOn? - PullRequest
1 голос
/ 18 июня 2019

Я пытаюсь проверить асинхронную функцию в собственном приложении реакции.

class myClass extends React.Component {

  ...

  closeModal = async () => {

    if (someCondition) {
      await myFunction1();
    } else {
      await myFunction2();
    }

    this.props.navigation.state.params.onGoBack();
    this.props.navigation.navigate('Main');
  };

  ...

}

Это мой тест:

const navigation = {
  navigate: jest.fn(),
  state: { params: { onGoBack: jest.fn() } },
};

const renderComponent = overrides => {
  props = {
    navigation,
    ...overrides,
  };

  return shallow(< myClass.wrappedComponent {...props} />);
};


describe('When the user presses the close icon', () => {
    it('should close the modal', () => {
      const wrapper = renderComponent();
      const instance = wrapper.instance();
      const spyCloseModal = jest.spyOn(instance, 'closeModal');
      instance().forceUpdate();
      component
        .find({ testID: 'close-icon' })
        .props()
        .onPress();
      expect(spyCloseModal).toHaveBeenCalled(); // this is passed
      expect(navigation.navigate).toHaveBeenCalled(); // this is not passed
    });
});

Похоже, что он застревает на вызовах await,Если я удалю ожидающие звонки, то это пройдетКто-то упомянул в другом посте, чтобы использовать .and.callThrough после spyOn, но он дает мне эту ошибку

Невозможно прочитать свойство 'CallThrough' из неопределенного

Ответы [ 2 ]

0 голосов
/ 21 июня 2019

Да, вы на правильном пути ... проблема в том, что closeModal является асинхронным.

await не завершено к тому времени, когда выполнение возвращается к тесту, поэтому this.props.navigation.navigate еще не был вызван.

Тест должен дождаться завершения closeModal, прежде чем будет подтверждено, что был вызван navigate.

closeModal - это функция asyncпоэтому он вернет Promise ...

... и вы можете использовать шпиона, чтобы получить Promise, который он возвращает ...

... тогда вы можете позвонитьawait на этом Promise в вашем тесте, чтобы убедиться, что closeModal завершено, прежде чем утверждать, что был вызван navigate.

Вот упрощенный рабочий пример для начала:

import * as React from 'react';
import { shallow } from 'enzyme';

class MyClass extends React.Component {
  closeModal = async () => {
    await Promise.resolve();
    this.props.navigation.navigate('Main');
  }
  render() { return <div onClick={() => this.closeModal()}></div> }
}

test('MyClass', async () => {  // <= async test function
  const props = { navigation: { navigate: jest.fn() }};
  const wrapper = shallow(<MyClass {...props} />);
  const instance = wrapper.instance();
  const spyCloseModal = jest.spyOn(instance, 'closeModal');
  wrapper.find('div').simulate('click');
  expect(spyCloseModal).toHaveBeenCalled();  // Success!
  const promise = spyCloseModal.mock.results[0].value;  // <= get the Promise returned by closeModal
  await promise;  // <= await the Promise
  expect(props.navigation.navigate).toHaveBeenCalled();  // Success!
})

Обратите внимание на использование mockFn.mock.results для получения Promise, возвращаемого closeModal.

0 голосов
/ 18 июня 2019

Одним из решений является сделать ваш тест async и запустить await (anything), чтобы разделить ваш тест на несколько микрозадач:

it('should close the modal', async () => {
      const wrapper = renderComponent();
      component
        .find({ testID: 'close-icon' })
        .props()
        .onPress();
      await Promise.resolve();
      expect(navigation.state.params.onGoBack).toHaveBeenCalled(); 
      expect(navigation.navigate).toHaveBeenCalledWith("Main");
    });

Полагаю, вам не нужен ни .forceUpdate, ни .spyOn в методе экземпляра. как только навигация происходит правильно, не имеет значения, каким внутренним методом она называется

больше на микрозадачу против макрозадачи: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f

альтернатива - использовать макрозадачу (setTimeout(...., 0))

it('should close the modal', (done) => {
      const wrapper = renderComponent();
      component
        .find({ testID: 'close-icon' })
        .props()
        .onPress();
      setTimeout(() => {
        expect(navigation.state.params.onGoBack).toHaveBeenCalled(); 
        expect(navigation.navigate).toHaveBeenCalledWith("Main");
        done();
    });
}
...