我必须为PlayerList
容器和Player
组件编写单元测试用例。编写分支和道具的测试用例是可以的,但我如何测试组件的方法及其中的逻辑。我的代码覆盖率不完整,因为这些方法未经过测试。
情境:
父组件将对其方法onSelect
的引用作为对子组件的回调传递。该方法在PlayerList
组件中定义,但Player
正在生成调用它的onClick事件。
父组件/容器:
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {selectTab} from '../actions/index';
import Player from './Player';
class PlayerList extends Component {
constructor(props){
super(props);
}
onSelect(i) {
if (!i) {
this.props.selectPlayer(1);
}
else {
this.props.selectPlayer(i);
}
}
createListItems(){
return this.props.playerList.map((item, i)=>{
return (
<Player key={i} tab={item} onSelect={() => this.onSelect(item.id)} />
)
});
}
render() {
return(
<div className="col-md-12">
<ul className="nav nav-tabs">
{this.createListItems()}
</ul>
</div>
)
}
}
function mapStateToProps(state){
return {
playerList: state.playerList
}
}
function matchDispatchToProps(dispatch){
return bindActionCreators({selectPlayer: selectPlayer}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(PlayerList);
子组件:
import React, { Component } from 'react';
class Player extends Component {
constructor(props){
super(props);
}
render() {
return(
<li className={this.props.player.selected?'active':''}>
<a href="#" onClick={() => this.props.onSelect(this.props.player.id)}>
<img src={this.props.player.imgUrl} className="thumbnail"/>
{this.props.player.name}
</a>
</li>
)
}
}
export default Player;
答案 0 :(得分:7)
.instance()
方法访问组件方法当然有几个先决条件。
shallow
或mount
函数渲染组件,具体取决于您是否需要 [和/或您喜欢的方式] {{ 3}}嵌套子节点上的事件。这也为您提供了一个酶包装器,您可以从中访问组件实例及其方法。示例:强>
// Import requisite modules
import React from 'react';
import sinon from 'sinon';
import { mount } from 'enzyme';
import { expect } from 'chai';
import PlayerList from './PlayerList';
// Describe what you'll be testing
describe('PlayerList component', () => {
// Mock player list
const playerList = [
{
id : 1,
imgUrl: 'http://placehold.it/100?text=P1',
name : 'Player One'
}
];
// Nested describe just for our instance methods
describe('Instance methods', () => {
// Stub the selectPlayer method.
const selectPlayer = sinon.stub();
// Full DOM render including nested Player component
const wrapper = mount(
<PlayerList playerList={ playerList } selectPlayer={ selectPlayer } />
);
// Get the component instance
const instance = wrapper.instance();
// Wrap the instance methods with spies
instance.createListItems = sinon.spy(instance.createListItems);
instance.onSelect = sinon.spy(instance.onSelect);
// Re-render component. Now with spies!
wrapper.update();
it('should call createListItems on render', () => {
expect(instance.createListItems).to.have.been.calledOnce;
});
it('should call onSelect on child link click', () => {
expect(instance.onSelect).to.not.have.been.called;
wrapper.find('li > a').at(0).simulate('click');
expect(instance.onSelect).to.have.been.calledOnce;
expect(instance.onSelect).to.have.been.calledWith(playerList[0].id);
});
});
});
备注:强>
PlayerList
和Player
使用上述代码时,我发现您没有将名为player
的道具分配给Player
;相反,你正在分配item={ item }
。为了在本地工作,我将其更改为<Player player={ item } … />
。onSelect
中,您正在检查收到的i
参数是否为假,然后调用selectPlayer(1)
。我在上面的例子中没有包含测试用例,因为逻辑关注我的原因有两个:
i
是否可能0
?如果是这样,它将始终评估为falsey并传递到该块。Player
拨打onSelect(this.props.player.id)
,我想知道this.props.player.id
是否会undefined
?如果是这样,我想知道为什么你会在props.playerList
中有一个没有id
属性的项目。但是如果你想像现在一样测试那个逻辑,它看起来就像这样......
onSelect
中的示例测试逻辑:
describe('PlayerList component', () => {
…
// Mock player list should contain an item with `id: 0`
// …and another item with no `id` property.
const playerList = [
…, // index 0 (Player 1)
{ // index 1
id : 0,
imgUrl: 'http://placehold.it/100?text=P0',
name : 'Player Zero'
},
{ // index 2
imgUrl: 'http://placehold.it/100?text=P',
name : 'Player ?'
}
];
describe('Instance methods', { … });
describe('selectPlayer', () => {
const selectPlayer = sinon.stub();
const wrapper = mount(
<PlayerList playerList={ playerList } selectPlayer={ selectPlayer } />
);
const instance = wrapper.instance();
// There is no need to simulate clicks or wrap spies on instance methods
// …to test the call to selectPlayer. Just call the method directly.
it('should call props.selectPlayer with `id` if `id` is truthy', () => {
instance.onSelect(playerList[0].id); // id: 1
expect(selectPlayer).to.have.been.calledOnce;
expect(selectPlayer).to.have.been.calledWith(playerList[0].id);
});
it('should call props.selectPlayer(1) if `id` is 0', () => {
instance.onSelect(playerList[1].id); // id: 0
expect(selectPlayer).to.have.been.calledTwice;
expect(selectPlayer).to.have.been.calledWith(1);
});
it('should call props.selectPlayer(1) if `id` is undefined', () => {
instance.onSelect(playerList[2].id); // undefined
expect(selectPlayer).to.have.been.calledThrice;
expect(selectPlayer).to.have.been.calledWith(1);
});
});
});
答案 1 :(得分:0)
您可以使用酶的simulate
函数来测试回调。您可以将回调函数作为来自sinon的间谍函数提供,并验证它是否已使用预期参数调用预期时间。
你可以在这里阅读更多内容:
https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/simulate.md
以下是模拟回调函数的Player和PlayerList组件的单元测试。您需要单独的PlayerList组件作为PlayerList和PlayerListContainer(指示组件连接到redux)。完成此操作后,您可以轻松测试您的PlayerList组件。
PlayerListTest.jsx:
import React from 'react';
import { shallow } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';
import PlayerList from 'components/PlayerList';
import Player from 'components/Player';
describe('PlayerList test', () => {
const playerList = [
{
id: '1',
imgUrl: 'testimageurl',
name: 'testplayer1'
},
{
id: '23423',
imgUrl: 'http://testimageurl2',
name: 'testplayer2'
},
{
id: '123124123',
imgUrl: 'http://testimageurl23',
name: 'testplayer142'
}
];
it('calls callback function with item id when player is selected', () => {
const mockSelectPlayer = sinon.spy();
const wrapper = shallow(<PlayerList playerList={playerList} selectPlayer={mockSelectPlayer} />);
const playerWrapper = wrapper.find(Player);
playerWrapper.at(0).simulate('select');
expect(mockSelectPlayer.calledOnce).to.equal(true);
expect(mockSelectPlayer.calledWith(playerList[0].id)).to.be.ok;
});
});
PlayerTest.jsx:
import React from 'react';
import { shallow } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';
import Player from 'components/Player.jsx';
describe('Player test', () => {
const player = {
id: '1234',
imgUrl: 'http://testimageurl',
name: 'testplayer'
};
it('calls callback function when the .player-container element clicked', () => {
const mockOnSelect = sinon.spy();
const wrapper = shallow(<Player player={player} onSelect={mockOnSelect} />);
wrapper.find('.player-container').simulate('click');
expect(mockOnSelect.calledOnce).to.equal(true);
});
});