我有一个使用React作为视图层的项目。为了测试它,我使用了mocha,karma,karma-webpack等。出于某种原因,在React 16+中,karma报告afterEach
在两种规格下已经运行了3次。当process.env.NODE_ENV
为development
而不是 production
时,这在React 16+中仅发生在 中,而仅发生在 中。
在以前对该问题的探讨中,规格失败的原因可能会级联并污染后续规格。为了帮助找出根本原因,这是我能找到的最简单的示例。
我试图追踪这种行为,但是被业力和套接字内部及其周围的复杂性所困扰。请考虑以下示例,该示例目前在https://github.com/jneander/react-mocha可用。
Example.js
import React, {Component} from 'react'
export default class Example extends Component {
render() {
try {
return (
<div>{this.props.foo.bar}</div>
)
} catch(e) {
console.log(e) // for logging purposes
throw e
}
}
}
Example.spec.js
import {expect} from 'chai'
import React from 'react'
import ReactDOM from 'react-dom'
class ExampleWrapper extends React.Component {
constructor(props) {
super(props)
this.state = {
error: false
}
}
componentDidCatch(error) {
console.log('there was a problem')
this.setState({
error: true
})
}
render() {
console.log('rendering!')
if (this.state.error) {
console.log('- rendering the error version')
return <div>An error occurred during render</div>
}
console.log('- rendering the real version')
return (
<Example {...this.props} />
)
}
}
import Example from './Example'
describe('Example', () => {
let $container
beforeEach(() => {
console.log('beforeEach')
$container = document.createElement('div')
document.body.appendChild($container)
})
afterEach(() => {
console.log('afterEach')
ReactDOM.unmountComponentAtNode($container)
$container.remove()
})
async function mount(props) {
return new Promise((resolve, reject) => {
const done = () => {
console.log('done rendering')
resolve()
}
ReactDOM.render(<ExampleWrapper {...props} />, $container, done)
})
}
it('fails this spec', async () => {
console.log('start test 1')
await mount({})
expect(true).to.be.true
})
it('also fails, but because of the first spec', async () => {
console.log('start test 2')
await mount({foo: {}})
expect(true).to.be.true
})
})
规格输出如下:
LOG LOG: 'beforeEach'
LOG LOG: 'start test 1'
LOG LOG: 'rendering!'
LOG LOG: '- rendering the real version'
Example
✗ fails this spec
Error: Uncaught TypeError: Cannot read property 'bar' of undefined (src/Example.spec.js:35380)
at Object.invokeGuardedCallbackDev (src/Example.spec.js:16547:16)
at invokeGuardedCallback (src/Example.spec.js:16600:31)
at replayUnitOfWork (src/Example.spec.js:31930:5)
at renderRoot (src/Example.spec.js:32733:11)
at performWorkOnRoot (src/Example.spec.js:33572:7)
at performWork (src/Example.spec.js:33480:7)
at performSyncWork (src/Example.spec.js:33452:3)
at requestWork (src/Example.spec.js:33340:5)
at scheduleWork (src/Example.spec.js:33134:5)
ERROR LOG: 'The above error occurred in the <Example> component:
in Example (created by ExampleWrapper)
in ExampleWrapper
React will try to recreate this component tree from scratch using the error boundary you provided, ExampleWrapper.'
LOG LOG: 'there was a problem'
LOG LOG: 'done rendering'
LOG LOG: 'rendering!'
LOG LOG: '- rendering the error version'
LOG LOG: 'afterEach'
LOG LOG: 'beforeEach'
LOG LOG: 'start test 2'
LOG LOG: 'rendering!'
LOG LOG: '- rendering the real version'
LOG LOG: 'done rendering'
✓ also fails, but because of the first spec
✓ also fails, but because of the first spec
LOG LOG: 'afterEach'
LOG LOG: 'afterEach'
Chrome 69.0.3497 (Mac OS X 10.13.6): Executed 3 of 2 (1 FAILED) (0.014 secs / NaN secs)
TOTAL: 1 FAILED, 2 SUCCESS
1) fails this spec
Example
Error: Uncaught TypeError: Cannot read property 'bar' of undefined (src/Example.spec.js:35380)
at Object.invokeGuardedCallbackDev (src/Example.spec.js:16547:16)
at invokeGuardedCallback (src/Example.spec.js:16600:31)
at replayUnitOfWork (src/Example.spec.js:31930:5)
at renderRoot (src/Example.spec.js:32733:11)
at performWorkOnRoot (src/Example.spec.js:33572:7)
at performWork (src/Example.spec.js:33480:7)
at performSyncWork (src/Example.spec.js:33452:3)
at requestWork (src/Example.spec.js:33340:5)
at scheduleWork (src/Example.spec.js:33134:5)
是什么原因导致重复的报告?
为什么这会在React 16+而不是React 15中发生?
我该如何解决?
答案 0 :(得分:0)
可能存在竞争条件,因为使用ref
函数解决了承诺。收到该组件引用并不表示初始渲染已完成。
如the reference所述,
如果需要引用根ReactComponent实例,则首选解决方案是将回调ref附加到根元素。
解决承诺的正确方法是使用render
回调参数,
如果提供了可选的回调,它将在呈现或更新组件之后执行。
应该是:
async function mount(props) {
return new Promise(resolve => {
ReactDOM.render(<Example {...props} />, $container, resolve)
})
}
该问题不会在第二次测试中发生,而会在第一次测试中发生,而不管是否存在第二次测试,并且不是特定于React 16.5。 它特定于React开发模式的工作方式。
这里的a simplified demo不包含摩卡因素。预期的错误是console.warn
的输出,但是有两个Error: Cannot read property 'bar' of undefined
的错误是console.error
,它们是由React本身输出的。 ReactDOM.render
运行组件render
的功能两次,并异步地输出来自第一次测试的错误。
The same demo与React的生产版本一起显示了一个Error: Cannot read property 'bar' of undefined
同步错误,这与预期的一样。失败的渲染不会使ReactDOM
渲染被拒绝,错误可能是caught by error boundary component if needed:
class EB extends Component {
componentDidCatch(err) {
this.props.onCatch(err);
}
render() {
return this.props.children;
}
}
async function mount(props) {
return new Promise((resolve, reject) => {
ReactDOM.render(<EB onCatch={reject}><Example {...props} /></EB>, $container, resolve)
})
}
在单元测试中不依赖React DOM渲染器是一个好习惯。酶可达到这个目的,并允许隔离地同步测试组件,尤其是shallow
wrapper。
答案 1 :(得分:0)
即使在包装器中使用componentDidCatch
,React 16+似乎在渲染期间也会出现未捕获的错误。这意味着Mocha将因未捕获的错误而使测试失败,然后继续下一个测试,此后组件的第二次渲染将完成并解决承诺,并运行一个断言。此操作在当前正在进行的测试中运行,从而导致本示例中看到的双重成功。
An issue已通过Github上的React仓库打开。