开玩笑:等待在 componentDidMount 中调用异步方法的 .then

Jest: Wait for called .then of async method in componentDidMount

我目前正在编写我的 React-App 测试。

我在我的 componentDidMount 方法中有一个异步调用,并在返回后更新状态。但是,我没有让它工作。

我找到了几个解决方案,None 似乎按预期工作。下面是我到达的最近点。

应用程序:

class App extends Component<{}, IState> {
    state: IState = {
        fetchedData: false
    };

    async componentDidMount() {
        await thing.initialize();
        this.test();
    }

    test = () => {
        this.setState({ fetchedData: true })
    };

    render() {
        return this.state.fetchedData ? (
            <div>Hello</div>
        ) : (
            <Spinner />
        );
    }
}

测试

it('Base test for app', async () => {
    const spy = spyOn(thing, 'initialize').and.callThrough();  // just for debugging
    const wrapper = await mount(<App />);
    // await wrapper.instance().componentDidMount();  // With this it works, but componentDidMount is called twice.
    wrapper.update();
    expect(wrapper.find('Spinner').length).toBe(0);
});

好吧,所以...thing.initialize 被调用(它是一个获取一些东西的异步方法)。 如果我明确调用 wrapper.instance().componentDidMount() 那么它会起作用,但是 componentDidMount 会被调用两次。

以下是我尝试过但 None 成功的想法:

应该不多,但谁能告诉我我少了哪一块?

如果这是集成测试你最好遵循等待方法说Selenium使用:也就是说,等到某个元素出现或超时。它应该如何编码取决于你使用的库(对于 Puppeter 它应该是 waitForSelector)。

一旦涉及到单元测试,那么我建议你采用不同的方法:

  1. 用您控制的 Promise 模拟每个外部依赖项(根据您的代码,很难说 automatic mock will work or you need to compose mock factory 是否有帮助,但其中一个或两者都有帮助)
  2. 初始化元素(我的意思是 运行 shallow()mount()
  3. 等到你的模拟被解决(额外的 await,使用 setTimeout(... ,0)flush-promises will work, check how microtasks/macrotasks works
  4. 断言元素的 render 并检查您的模拟是否已被调用

最后:

  1. 直接设置状态
  2. mocking/spying 关于内部方法
  3. 验证状态

都会导致不稳定的测试,因为它是您在单元测试期间不应该担心的实现细节。而且无论如何都很难和他们一起工作。

所以你的测试看起来像:

import thing from '../thing';
import Spinner from '../../Spinner';
import flushPromises from 'flush-promises';

it('loads data and renders it', async () => {
  jest.mock('../thing'); // thing.implementation is already mocked with jest.fn()
  thing.initialize.mockReturnValue(Promise.resolve(/*data you expect to return*/));
  const wrapper = shallow(<App />);
  expect(wrapper.find(Spinner)).toHaveLength(1);
  expect(wrapper.find(SomeElementYouRenderWithData)).toHaveLength(0);
  await flushPromises();
  expect(wrapper.find(Spinner)).toHaveLength(0);
  expect(wrapper.find(SomeElementYouRenderWithData)).toHaveLength(1);
})

或者您可以测试组件在拒绝时的行为:

import thing from '../thing';
import Spinner from '../../Spinner';
import flushPromises from 'flush-promises';

it('renders error message on loading failuer', async () => {
  jest.mock('../thing'); // thing.implementation is already mocked with jest.fn()
  thing.initialize.mockReturnValue(Promise.reject(/*some error data*/));
  const wrapper = shallow(<App />);
  expect(wrapper.find(Spinner)).toHaveLength(1);
  await flushPromises();
  expect(wrapper.find(Spinner)).toHaveLength(0);
  expect(wrapper.find(SomeElementYouRenderWithData)).toHaveLength(0);
  expect(wrapper.find(SomeErrorMessage)).toHaveLength(1);
})