使用 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/
我似乎想不通。我正在使用 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/