如何在 React-Redux 中使用 Mocha、Chai 和 Enzyme 测试方法和回调

2024-03-13

我必须为一个编写单元测试用例PlayerList容器和Player成分。为分支和 props 编写测试用例是可以的,但是如何测试组件的方法及其内部的逻辑。我的代码覆盖率不完整,因为这些方法未经测试。

设想:

父组件传递对其方法的引用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;

使用酶的.instance()访问组件方法的方法

当然,有几个先决条件。

  1. 您必须首先使用酶渲染组件一次shallow http://airbnb.io/enzyme/docs/api/shallow.html or mount http://airbnb.io/enzyme/docs/api/mount.html功能取决于您是否需要[和/或您喜欢的方式] simulate http://airbnb.io/enzyme/docs/api/ReactWrapper/simulate.html嵌套子级上的事件。这还为您提供了一个酶包装器,您可以从中访问组件实例及其方法。
  2. 你需要包裹诗农测试间谍 http://sinonjs.org/docs/#spies围绕这些实例方法并使用重新渲染.update http://airbnb.io/enzyme/docs/api/ReactWrapper/update.html获取带有间谍的包装器版本,您可以对其进行断言。

Example:

// 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);
    });
  });
});

Notes:

  • 当使用上面的代码时PlayerList and Player,我发现你没有分配一个名为player to Player;相反,你正在分配item={ item }。为了使其在本地工作,我将其更改为<Player player={ item } … />.
  • In onSelect you are checking whether the received i argument is falsey and then calling selectPlayer(1). I didn't include a test case for this in the above example because the logic concerns me for a couple of reasons:
    1. 我怀疑是否i可能永远是0?如果是这样,它将始终评估为 false 并传递到该块中。
    2. Because Player calls 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);
    });
  });
});
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 React-Redux 中使用 Mocha、Chai 和 Enzyme 测试方法和回调 的相关文章

随机推荐