如何使用 jest 对具有异步 componentDidMount 的 HOC 进行单元测试?

How do I unit test a HOC having an async componentDidMount using jest?

这是我的反应 HOC:

import React from "react"
import { getData } from 'services/DataService'

export function withData(WrappedComponent, count) {
  return class extends React.Component {
    state = { 
      data: [] 
    }

    async componentDidMount() {
      this.setState({ 
        data: await getData(count) 
      })
    }

    render() {
      const { data } = this.state
      return data.length > 0 && <WrappedComponent data={data} />
    }
  }
}

我想写一个单元测试来证明如果 getData(count) returns 一些数据, WrappedComponent 被正确呈现或者至少 state.data.length 大于零.

我在测试中用有效的 mockResponse:

像这样嘲笑了 getData
  jest.mock("services/DataService", () => ({
    getData: jest.fn().mockImplementation(() => Promise.resolve(mockResponse))
  }))

但我卡在了这一点上。社区有一些关于测试具有异步 componentDidMount 的组件的答案,但其中 none 与 HOC 相关。我试过这样的事情:

const WithReleaseNotesComponent = withReleaseNotes(mockedComponent)

然后尝试实例化或挂载 WithReleaseNotesComponent,但我从未见过他们拥有我可以测试的权利 state/props。 state/props 总是空的。我错过了什么?

我将使用 enzyme 库来测试您的组件。为了保证使用async/await语法的componentDidMount生命周期方法的异步任务执行完成,我们需要flush promise队列

有关刷新承诺队列如何执行的更多信息,请参阅macrotasks-and-microtasks

All microtasks are completed before any other event handling or rendering or any other macrotask takes place.

这意味着 async/await 和 promise 是微任务,它们将在 setTimeout 创建的宏任务之前完成。

例如

index.tsx:

import React from 'react';
import { getData } from './services/DataService';

export function withData(WrappedComponent, count) {
  return class extends React.Component {
    state = {
      data: [],
    };

    async componentDidMount() {
      this.setState({
        data: await getData(count),
      });
    }

    render() {
      const { data } = this.state;
      return data.length > 0 && <WrappedComponent data={data} />;
    }
  };
}

index.test.tsx:

import React from 'react';
import { withData } from './';
import { getData } from './services/DataService';
import { shallow } from 'enzyme';
import { mocked } from 'ts-jest/utils';

jest.mock('./services/DataService');

const mockedGetData = mocked(getData);

class MockedComponent extends React.Component {
  render() {
    return <div>mocked component</div>;
  }
}

function flushPromises() {
  return new Promise((resolve) => setTimeout(resolve, 0));
}

describe('66260393', () => {
  afterAll(() => {
    jest.resetAllMocks();
  });
  it('should render wrapped component', async () => {
    mockedGetData.mockResolvedValueOnce('fake data');
    const WithDataComponent = withData(MockedComponent, 1);
    const wrapper = shallow(<WithDataComponent></WithDataComponent>);
    expect(wrapper.state('data')).toEqual([]);
    await flushPromises();
    expect(wrapper.state('data')).toEqual('fake data');
    expect(wrapper.find(MockedComponent)).toBeTruthy();
    expect(wrapper.find(MockedComponent).prop('data')).toEqual('fake data');
  });
});

单元测试结果:

 PASS  examples/66260393/index.test.tsx
  66260393
    ✓ should render wrapped component (23 ms)

-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |      90 |      100 |      80 |      90 |                   
 66260393          |     100 |      100 |     100 |     100 |                   
  index.tsx        |     100 |      100 |     100 |     100 |                   
 66260393/services |      50 |      100 |       0 |      50 |                   
  DataService.ts   |      50 |      100 |       0 |      50 | 2                 
-------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.198 s

问题出在我的模拟上。我不知道为什么,但 jest.mock() 没有按预期工作。我用 jest.spyOn() 替换了它,然后用 await 调用了 shallow。一切都很顺利。甚至没有任何兑现承诺的必要。

withDataHOC.spec.js

import React from 'react'
import { shallow } from 'enzyme'
import { withData } from 'components/higher-order-components/WithDataHOC'
import * as dataService from 'services/DataService'

describe("WithDataHOC", () => {
  afterAll(() => {
    jest.resetAllMocks()
  })

  const mockedComponent = jest.fn()

  it("doesn't render anything if there are no data", async() => {    
    jest.spyOn(dataService, "getData").mockImplementation( () => Promise.resolve()) 
    const WithDataComponent = withData(mockedComponent)
    const wrapper = shallow(<WithDataComponent />)
    expect(wrapper).toMatchSnapshot()
  })
  const mockResponse = JSON.parse(require('services/__mocks__/DataResponse').dataResponseCache)

  it("renders correctly if there are data", async() => {     
    jest.spyOn(dataService, "getData").mockImplementation( () => Promise.resolve(mockResponse)) 
      const WithDataComponent = withData(mockedComponent)
      const wrapper = await shallow(<WithDataComponent />)            
      expect(wrapper).toMatchSnapshot()        
  })
})