How to test methods and callback with Mocha, Chai and Enzyme in React-Redux

I need to write unit test for the container PlayerListand Player. Writing test cases for branches and details is OK, but how to check component methods and logic inside them. My code coverage is incomplete because the methods are not tested.

Scenario:

The parent component passes the reference to its method onSelectas a callback to the child component. The method is defined in the component PlayerList, but Playergenerates an onClick event that raises it.

Parent component / container:

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

Children's component:

    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;
+4
source share
2 answers

.instance()

, .

:

// 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? , .
    • 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);
    });
  });
});
+7

simulate . , . : https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/simulate.md

Player PlayerList, . PlayerList PlayerList PlayerListContainer (, ). 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);
  });
});
0

Source: https://habr.com/ru/post/1654046/


All Articles