如何使用 spyOn 测试异步功能?

HTB*_*HTB 6 javascript unit-testing jestjs react-native enzyme

我正在尝试在 React Native 应用程序中测试异步函数。

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

  ...

}
Run Code Online (Sandbox Code Playgroud)

这是我的测试:

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
    });
});
Run Code Online (Sandbox Code Playgroud)

看起来它卡在等待调用上。如果我删除等待调用然后它通过。有人在另一篇文章中提到在.and.callThroughspyOn 之后使用,但它给了我这个错误

无法读取未定义的属性“callThrough”

sky*_*yer 7

解决方案之一是进行测试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");
    });
Run Code Online (Sandbox Code Playgroud)

我相信您既不需要.forceUpdate也不.spyOn需要实例方法。一旦导航正确发生,调用什么内部方法并不重要

有关微任务与宏任务的更多信息:https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f

另一种方法是使用 Macrotask( 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();
    });
}
Run Code Online (Sandbox Code Playgroud)