使用 Jest 和 create-react-app 测试 React Async

Testing React Async with Jest and create-react-app

我似乎想不通。我正在使用 create-react-app,它内置于测试 运行ner Jest 中。对于所有同步代码,它似乎都工作得很好,但是当模拟 promises 时我似乎无法让它工作。

React 组件有一个我可以模拟提交的表单。

React 组件代码片段。

//Top of the page
import {auth} from '../../lib/API_V2'
// ... //

// Handle submit runs when the form is submitted
handleSubmit = (event) => {
  console.log('submit')
  event.preventDefault()
  this.setState(prevState => ({
    ...prevState,
    loading: true
  }))
  console.log('stateSet')
  auth(this.state.userName, this.state.password)
    .then(results => {
      // NEVER RUNS
      console.log('then')
      // stuff omitted
      this.setState(prevState => ({
        ...prevState,
        loading: false
      }))
      this.props.afterAuth()
    })
  .catch(() => {
    // also never runs
    // omitted
    this.setState(prevState => ({
      ...prevState,
      loading: false
    }))
    this.props.afterAuth()
  })
}

测试代码

jest.mock('../../lib/API_V2')
it.only(`should mock a login`, () => {
  const myMock = jest.fn()
  const authComp = mount(<AuthComponent afterAuth={myMock}/>)

  authComp.find('.userName').simulate('change', {target: {value: 'userName'}})
  authComp.find('.password').simulate('change', {target: {value: 'password'}})
  expect(authComp.state().userName).toEqual('userName')
  expect(authComp.state().password).toEqual('password')
  authComp.find('[type="submit"]').get(0).click()
  expect(myMock.mock.calls.length).toBe(1) // FAILS
})

API lib returns 一个承诺。我没有使用它,而是在它旁边有一个 __mocks__/API_V2.js 。看起来像这样

function auth (lastname, accountNumber) {
  console.log('yay!?')
  return new Promise((resolve) => {
    resolve({
      accountNumber,
      lastName: lastname
    })
  })
}     

我的模拟测试代码似乎从来没有运行。如果我记录模拟函数,我会得到 function auth() {return mockConstructor.apply(this,arguments);}

我已尝试按照说明进行操作 https://facebook.github.io/jest/docs/tutorial-async.html,但似乎没有调用我的模拟方法。实际方法也不是。相反,我调用 auth() returns undefined.

有人有什么想法吗?

-- 补充信息--

src
  Components
    AuthComponent
      AuthComponent.js
      AuthComponent.test.js
      index.js
  Lib
    API_V2
      API_V2.js
      index.js
      __mocks__
        API_V2.js

我认为您遇到了与此问题相关的错误:https://github.com/facebook/jest/issues/2070

由于您实际上是在尝试导入名为 API_V2/index.js 的文件,因此您需要模拟 index.js。但是,你这样做会很糟糕,因为它将成为你尝试模拟的 每个 index.js 文件的有效模拟。

目前执行此操作的最佳方法是重写一些代码以使用 dependency-injection 并将模拟传递给需要使用 { auth }

的任何内容

在您模拟的新 Promise 中,即使您立即解析,该解析也不会同步发生。 Promise 回调总是 运行 作为一个排队的 微任务 ,所以当你在你的测试中模拟点击时,你的 mock 中的 Promise 回调还没有 运行(所以myMock 也还没有被调用)。这就是您的期望失败的原因。

解决此问题的一种(有点老套)方法是使用 setTimeout。 setTimeout 会将 task 入队,任务总是 运行 在 microtask 之后。 Jest 通过从 it 回调中返回 Promises 来支持异步测试,所以你可以这样写:

jest.mock('../../lib/API_V2')
it.only(`should mock a login`, () => new Promise(resolve => {
  const myMock = jest.fn()
  const authComp = mount(<AuthComponent afterAuth={myMock}/>)

  authComp.find('.userName').simulate('change', {target: {value: 'userName'}})
  authComp.find('.password').simulate('change', {target: {value: 'password'}})
  expect(authComp.state().userName).toEqual('userName')
  expect(authComp.state().password).toEqual('password')
  authComp.find('[type="submit"]').get(0).click()
  setTimeout(() => {
    expect(myMock.mock.calls.length).toBe(1)
    resolve() // Tell jest this test is done running
  }, 0);
}))

这里有关于任务和微任务如何工作的很好的解释:https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/