测试异步嵌套组件
Test asynchronous nested component
假设我有以下包装器组件:
'use strict'
import React, {PropTypes, PureComponent} from 'react'
import {update} from '../../actions/actions'
import LoadFromServerButton from '../LoadFromServerButton'
import {connect} from 'react-redux'
export class FooDisplay extends PureComponent {
render () {
return (
<p>
<span className='foo'>
{this.props.foo}
</span>
<LoadFromServerButton updateFunc={this.props.update} />
</p>
)
}
}
export const mapStateToProps = (state) => {
return {foo: state.foo.foo}
}
FooDisplay.propTypes = {
foo: PropTypes.string
}
export const mapDispatchToProps = (dispatch) => {
return {
update: (foo) => dispatch(update(foo))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(FooDisplay)
和以下内部组件:
'use strict'
import React, {PropTypes, PureComponent} from 'react'
import {get} from '../../actions/actions'
import ActiveButton from '../ActiveButton'
import {connect} from 'react-redux'
export class LoadFromServerButton extends PureComponent {
doUpdate () {
return this.props.get().then(this.props.updateFunc)
}
render () {
return (
<ActiveButton action={this.doUpdate.bind(this)} actionArguments={[this.props.foo]} text='fetch serverside address' />
)
}
}
export const mapStateToProps = (state) => {
return {foo: state.foo.foo}
}
export const mapDispatchToProps = (dispatch) => {
return {
get: () => dispatch(get())
}
}
LoadAddressFromServerButton.propTypes = {
updateFunc: PropTypes.func.isRequired
}
export default connect(mapStateToProps, mapDispatchToProps)(LoadFromServerButton)
ActiveButton
是一个非常薄的按钮包装器,带有点击和参数解构。
现在假设我的get action是这样写的:
export const get = () => dispatch => http('/dummy_route')
.spread((response, body) => dispatch(actOnThing(update, body)))
现在如果我这样写测试:
/* global window, test, expect, beforeAll, afterAll, describe */
'use strict'
import React from 'react'
import FooDisplay from './index'
import {mount} from 'enzyme'
import {Provider} from 'react-redux'
import configureStore from '../../store/configureStore'
import nock, {uriString} from '../../config/nock'
import _ from 'lodash'
const env = _.cloneDeep(process.env)
describe('the component behaves correctly when integrating with store and reducers/http', () => {
beforeAll(() => {
nock.disableNetConnect()
process.env.API_URL = uriString
})
afterAll(() => {
process.env = _.cloneDeep(env)
nock.enableNetConnect()
nock.cleanAll()
})
test('when deep rendering, the load event populates the input correctly', () => {
const store = configureStore({
address: {
address: 'foo'
}
})
const display = mount(<Provider store={store}><FooDisplay /></Provider>,
{attachTo: document.getElementById('root')})
expect(display.find('p').find('.address').text()).toEqual('foo')
const button = display.find('LoadFromServerButton')
expect(button.text()).toEqual('fetch serverside address')
nock.get('/dummy_address').reply(200, {address: 'new address'})
button.simulate('click')
})
})
这导致:
Unhandled rejection Error: Error: connect ECONNREFUSED 127.0.0.1:8080
经过一番思考,这是因为测试没有 return 承诺,因为单击按钮会导致承诺在幕后触发,因此,afterAll
立即运行,清理箭尾,真正的 http 连接通过网络。
如何测试这个案例?我似乎没有一种简单的方法来 return 正确的承诺...我如何测试这些更新对 DOM 的更新?
正如您提到的,问题是您没有通过测试 return 的承诺。因此,要使 get
return 成为一个已知的承诺,您可以直接模拟 get
而无需使用 nock:
import {get} from '../../actions/actions'
jest.mock('../../actions/actions', () => ({get: jest.fn}))
这将用对象替换动作模块{get: jestSpy}
然后在您的测试中您可以创建一个承诺并让 get
return 这个以及 return 这个来自您测试的承诺:
it('', ()=>{
const p = new Promise.resolve('success')
get.mockImplementation(() => p)//let get return the resolved promise
//test you suff
return p
})
为了只模拟导入模块的一个方法,使用 .requireActual(...)
jest.mock('../your_module', () => ({
...(jest.requireActual('../your_module')),
YourMethodName: () => { return { type: 'MOCKED_ACTION'}; }
}));
假设我有以下包装器组件:
'use strict'
import React, {PropTypes, PureComponent} from 'react'
import {update} from '../../actions/actions'
import LoadFromServerButton from '../LoadFromServerButton'
import {connect} from 'react-redux'
export class FooDisplay extends PureComponent {
render () {
return (
<p>
<span className='foo'>
{this.props.foo}
</span>
<LoadFromServerButton updateFunc={this.props.update} />
</p>
)
}
}
export const mapStateToProps = (state) => {
return {foo: state.foo.foo}
}
FooDisplay.propTypes = {
foo: PropTypes.string
}
export const mapDispatchToProps = (dispatch) => {
return {
update: (foo) => dispatch(update(foo))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(FooDisplay)
和以下内部组件:
'use strict'
import React, {PropTypes, PureComponent} from 'react'
import {get} from '../../actions/actions'
import ActiveButton from '../ActiveButton'
import {connect} from 'react-redux'
export class LoadFromServerButton extends PureComponent {
doUpdate () {
return this.props.get().then(this.props.updateFunc)
}
render () {
return (
<ActiveButton action={this.doUpdate.bind(this)} actionArguments={[this.props.foo]} text='fetch serverside address' />
)
}
}
export const mapStateToProps = (state) => {
return {foo: state.foo.foo}
}
export const mapDispatchToProps = (dispatch) => {
return {
get: () => dispatch(get())
}
}
LoadAddressFromServerButton.propTypes = {
updateFunc: PropTypes.func.isRequired
}
export default connect(mapStateToProps, mapDispatchToProps)(LoadFromServerButton)
ActiveButton
是一个非常薄的按钮包装器,带有点击和参数解构。
现在假设我的get action是这样写的:
export const get = () => dispatch => http('/dummy_route')
.spread((response, body) => dispatch(actOnThing(update, body)))
现在如果我这样写测试:
/* global window, test, expect, beforeAll, afterAll, describe */
'use strict'
import React from 'react'
import FooDisplay from './index'
import {mount} from 'enzyme'
import {Provider} from 'react-redux'
import configureStore from '../../store/configureStore'
import nock, {uriString} from '../../config/nock'
import _ from 'lodash'
const env = _.cloneDeep(process.env)
describe('the component behaves correctly when integrating with store and reducers/http', () => {
beforeAll(() => {
nock.disableNetConnect()
process.env.API_URL = uriString
})
afterAll(() => {
process.env = _.cloneDeep(env)
nock.enableNetConnect()
nock.cleanAll()
})
test('when deep rendering, the load event populates the input correctly', () => {
const store = configureStore({
address: {
address: 'foo'
}
})
const display = mount(<Provider store={store}><FooDisplay /></Provider>,
{attachTo: document.getElementById('root')})
expect(display.find('p').find('.address').text()).toEqual('foo')
const button = display.find('LoadFromServerButton')
expect(button.text()).toEqual('fetch serverside address')
nock.get('/dummy_address').reply(200, {address: 'new address'})
button.simulate('click')
})
})
这导致:
Unhandled rejection Error: Error: connect ECONNREFUSED 127.0.0.1:8080
经过一番思考,这是因为测试没有 return 承诺,因为单击按钮会导致承诺在幕后触发,因此,afterAll
立即运行,清理箭尾,真正的 http 连接通过网络。
如何测试这个案例?我似乎没有一种简单的方法来 return 正确的承诺...我如何测试这些更新对 DOM 的更新?
正如您提到的,问题是您没有通过测试 return 的承诺。因此,要使 get
return 成为一个已知的承诺,您可以直接模拟 get
而无需使用 nock:
import {get} from '../../actions/actions'
jest.mock('../../actions/actions', () => ({get: jest.fn}))
这将用对象替换动作模块{get: jestSpy}
然后在您的测试中您可以创建一个承诺并让 get
return 这个以及 return 这个来自您测试的承诺:
it('', ()=>{
const p = new Promise.resolve('success')
get.mockImplementation(() => p)//let get return the resolved promise
//test you suff
return p
})
为了只模拟导入模块的一个方法,使用 .requireActual(...)
jest.mock('../your_module', () => ({
...(jest.requireActual('../your_module')),
YourMethodName: () => { return { type: 'MOCKED_ACTION'}; }
}));