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

时间:2019-06-18 18:16:05

标签: javascript unit-testing react-native jestjs 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');
  };

  ...

}

这是我的测试

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调用中。如果我删除了等待呼叫,那么它将过去。有人在另一篇文章中提到在spyOn之后使用.and.callThrough,但这给了我这个错误

  

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

2 个答案:

答案 0 :(得分:0)

解决方案之一是进行测试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

替代方法是使用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();
    });
}

答案 1 :(得分:0)

是的,您的方向正确……问题是closeModal是异步的。

在执行返回测试时,await尚未完成,因此尚未调用this.props.navigation.navigate

在断言closeModal被调用之前,测试需要等待navigate完成。

closeModalasync函数,因此它将返回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

相关问题