Getsuga
Getsuga

Reputation: 609

How to achieve a 100% test coverage on these ternary expressions in react components

I'm trying to learn test-driven development, so I have created a simple training app composed of 2 simple working components App.js which saves an array of data coming from a dummy local JSON file to the App.js component state then map through it and render a User component with props for each element of the data array. So I have 3 uncovered lines which all contain "if statements" and, I want to achieve 100% test coverage on them, so please help. Here are the results of the tests. enter image description here

these are the uncovered lines for App.js from line 18 and 32 are the ternary expressions in each function

clickFollowHandler(id) {
    this.setState(prevState => {
        const updatedUsers = prevState.users.map(user => {
            if (user.id === id) {
              user.isFollowed === 'active' ? user.isFollowed = 'idle' : user.isFollowed = 'active'
            }
            return user
        })
        return {
            users: updatedUsers
        }
    })
  }

clickStarHandler(id) {
    this.setState(prevState => {
        const updatedUsers = prevState.users.map(user => {
            if (user.id === id) {
              user.isStared === 'active' ? user.isStared = 'idle' : user.isStared = 'active'
            }
            return user
        })
        return {
            users: updatedUsers
        }
    })
  }

and this is User.js line 23 is the checkbox ternary expression

return(
      <div className={classes.container} key={id}>
        <img className={classes.imageContainer} src={myImage} alt={name} />
        <div className={classes.contentContainer}>
          <div className={classes.contentContainerRow1}>
            <div className={classes.name}>name: {name}</div>
            <button onClick={() => handleFollowClick(id)}>
              {isFollowed === 'active' ? 'Unfollow' : 'Follow'}
            </button>
          </div>
          <div className={classes.contentContainerRow2}>
            <div className={classes.date}>date: {date}</div>
            <div className={classes.time}>reading time: {readingTime}</div>
            <input 
              className={classes.hvrIconPop}
              checked={isStared === 'active' ? true : false} 
              onChange={() => handleStarClick(id)}
              type='checkbox' 
            />
          </div>
        </div>
      </div>
    )

Now this is my App.test.js

import React from 'react';
import ReactDOM from 'react-dom';
import {shallow, mount} from './enzyme';
import App from './App';

jest.mock('./data/users-data.json')
let {user} = require('./data/users-data.json')


describe('App Component', () => {
  it('calling the clickFollowHandler method from App Component has the expected effect on the state of the first user', () => {
    const AppComponent = shallow(<App />)
    const wrapper = AppComponent.instance()
    wrapper.clickFollowHandler('5d552d0058f193f2795fc814')
    expect(wrapper.state.users[0].isFollowed).toMatch('idle')
  })
})




describe('App Component', () => {
  it('calling the clickStarHandler method from App Component has the expected effect on the state of the second user', () => {
    const AppComponent = shallow(<App />)
    const wrapper = AppComponent.instance()
    wrapper.clickStarHandler('5d552d00b20b141dff10d2a2')
    expect(wrapper.state.users[1].isStared).toEqual('idle')
  })
})

and my User.test.js

import React from 'react';
import renderer from 'react-test-renderer';
import {shallow, mount} from '../../enzyme';

import User from './User';

const users = {
  "id": "5d552d0058f193f2795fc814",
  "isFollowed": "active",
  "isStared": "idle",
  "image": "./assets/images/avata.png",
  "readingTime": 20,
  "name": "Walton Morton",
  "date": "Aug 9"
};

it('renders correctly when there are no users', () => {
  const tree = renderer.create(<User 
    key={''}
    id={''}
    name={''}
    date={''}
    readingTime={''}
    isStared={''}
    isFollowed={''}
    image={''}
    handleFollowClick={() => {}}
    handleStarClick={() => {}} 
  />).toJSON();
  expect(tree).toMatchSnapshot();
});

it('renders correctly when there is one user', () => {

  const tree = renderer.create(<User 
    key={users.id}
    id={users.id}
    name={users.name}
    date={users.date}
    readingTime={users.readingTime}
    isStared={users.isStared}
    isFollowed={users.isFollowed}
    image={users.image}
    handleFollowClick={() => 'test'}
    handleStarClick={() => {}}
  />).toJSON();
  expect(tree).toMatchSnapshot();
});




it('when the follow button is clicked a callback is executed', () => {
  const mockFollowClick = jest.fn();
  const mockStarClick = jest.fn();

  const tree = renderer.create(<User 
    key={users.id}
    id={users.id}
    name={users.name}
    date={users.date}
    readingTime={users.readingTime}
    isStared={users.isStared}
    isFollowed={users.isFollowed}
    image={users.image}
    handleFollowClick={mockFollowClick}
    handleStarClick={mockStarClick}
  />)

  const button = tree.root.findByType('button');
  const input = tree.root.findByType('input');

  button.props.onClick();
  expect(mockFollowClick).toHaveBeenCalled();

  button.props.onClick();
  expect(mockFollowClick).toHaveBeenCalledWith('5d552d0058f193f2795fc814');

  input.props.onChange();
  expect(mockStarClick).toHaveBeenCalled();
})





describe('User Component', () => {
  it('clicking on the button will trigger the click handler', () => {
    const mockFollowHandler = jest.fn();
    const mockStarHandler = jest.fn();
    const wrapper = mount(<User 
      key={users.id}
      id={users.id}
      name={users.name}
      date={users.date}
      readingTime={users.readingTime}
      isStared={users.isStared}
      isFollowed={users.isFollowed}
      image={users.image}
      handleFollowClick={mockFollowHandler} 
      handleStarClick={mockStarHandler} 
    />)
    wrapper.find('button').simulate('click');
    expect(mockFollowHandler).toHaveBeenCalledWith('5d552d0058f193f2795fc814')
  })

  it('changing the star checkbox will trigger an onChange handler', () => {
    const mockFollowHandler = jest.fn();
    const mockStarHandler = jest.fn();
    const wrapper = mount(<User 
      key={users.id}
      id={users.id}
      name={users.name}
      date={users.date}
      readingTime={users.readingTime}
      isStared={users.isStared}
      isFollowed={users.isFollowed}
      image={users.image}
      handleFollowClick={mockFollowHandler} 
      handleStarClick={mockStarHandler} 
    />)
    wrapper.find('input').simulate('change');
    expect(mockStarHandler).toHaveBeenCalledWith('5d552d0058f193f2795fc814');
  })
});

Upvotes: 3

Views: 4589

Answers (1)

Matt Carlotta
Matt Carlotta

Reputation: 19772

You need to test against both conditions of the ternary expression (not just one). Since I can't see the complete code, I'll try to offer some brief notes:

For clickStarHandler and clickFollowHandler, you'll need to test against users state and manually invoke the class fields (as you've done). However, you'll need to satisfy the condition when the isFollowed/isStarred matches active and when it matches idle. Fairly straight forward process, however, if you need to update the users state to include some data, then you can simply use wrapper.setState({ users: userData });.

For testing the App:

import React from 'react";
import { mount } from "enzyme";
import App from "../App";

// for simplicity, I'd recommend only using one user
const usersData = [
  {
    id: "5d552d0058f193f2795fc814",
    isFollowed: "active",
    isStarred: "idle",
    image: "./assets/images/avatar.png",
    readingTime: 20,
    name: "Walton Morton",
    date: "Aug 9"
  }
];

// *optional* include any props that are needed for the App 
const initialProps = {};

describe("App Component", () => {
  let wrapper;
  beforeEach(() => (
    wrapper = mount(<App { ...initialProps }/>);
    wrapper.setState({ users: usersData }); // not needed if user data is already defined in state
  )}

  it("sets the 'user.isStarred' state to 'active' or 'idle'", () => (
    const invokeStarHandler = () => {
      wrapper.instance().clickStarHandler("5d552d0058f193f2795fc814");
      wrapper.update();
    };

    invokeStarHandler();

    expect(wrapper.state("users[0].isStarred").toEqual("active");

    invokeStarHandler();

    expect(wrapper.state("users[0].isStarred").toEqual("idle");
  });

  it("sets the 'user.isFollowed' state to 'active' or 'idle'", () => (
    const invokeFollowHandler = () => {
      wrapper.instance().clickFollowHandler("5d552d0058f193f2795fc814");
      wrapper.update();
    };

    invokeFollowHandler();

    expect(wrapper.state("users[0].isFollowed").toEqual("idle");

    invokeFollowHandler();

    expect(wrapper.state("users[0].isFollowed").toEqual("active");
  });

  ...etc.
});

For Users testing, simply manipulate the user props; for example, changing wrapper.setProps({ isStarred: "active" }) or wrapper.setProps({ isStarred: "idle" }), then searching for the input and testing against its props:

import React from 'react";
import { mount } from "enzyme";
import Users from "../Users";

// include any props that are needed for the Users component 
const initialProps = {
  id: "5d552d0058f193f2795fc814",
  isFollowed: "active",
  isStarred: "idle",
  image: "./assets/images/avatar.png",
  readingTime: 20,
  name: "Walton Morton",
  date: "Aug 9"
}

describe("Users Component", () => {
  let wrapper;
  beforeEach(() => (
    wrapper = mount(<Users { ...initialProps }/>);
  )}

  it("updates the input's 'checked' property based upon a 'isStarred' prop", () => (    
   expect(wrapper.find("input").props().checked).toBeFalsy();

   wrapper.setProps({ isStarred: "active" });

   expect(wrapper.find("input").props().checked).toBeTruthy();
  });

  ...etc
});

On a side note, you can use object destructuring and the spread syntax to vastly simply your React component code.

Upvotes: 4

Related Questions