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

时间:2017-02-15 20:52:24

标签: javascript reactjs jestjs create-react-app

我似乎无法想出这个。我正在使用create-react-app,它是在测试运行器Jest中构建的。对于所有同步代码,它似乎工作得很好,但是当嘲笑承诺时,我似乎无法让它工作。

反应组件的形式我可以模拟提交。

反应组件代码段。

//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返回一个promise。而不是使用它,我旁边有一个__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()的调用返回undefined。

有人有什么想法吗?

- 补充资料 -

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

2 个答案:

答案 0 :(得分:3)

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

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

目前执行此操作的最佳方法是重写一些代码以使用依赖注入并将模拟传递给需要使用的任何代码{ auth }

答案 1 :(得分:2)

在模拟的新Promise中,即使您立即解决,此分辨率也不会同步发生。 Promise回调总是以排队的微任务运行,因此当您在测试中模拟点击时,模拟中的Promise回调尚未运行(因此myMock尚未被调用,其一)。这就是你期望失败的原因。

你可以解决这个问题的一种(有点hacky)方法是使用setTimeout。 setTimeout将排队任务,任务总是在微任务之后运行。 Jest通过返回来自it回调的Promise来支持异步测试,因此您可以编写:

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/