React component testing… method calls, mocking components and faking time.

At 15gifts we’ve been busy rebuilding the front-end of our decision engine with React and Redux.

When writing our unit tests it wasn’t immediately clear how to:

  • Test method calls on component classes.
  • Mock components within other components.
  • Test state changes after a setTimeout().

Here are some snippets that show the techniques we’ve found useful to complete the above… I’m using Mocha, Chai, Enzyme, Sinon and Rewire in the examples.


Testing method calls on component classes

If you’re using Redux, a lot of the time your component will call actions on events like ‘click’. In this component when the user clicks a div we would like to call a handleClick method instead.

class Button extends Component {
render() {
return (
<div onClick={()=>this.handleClick()}>Click me!</div>
)
}
  handleClick() {
alert('Clicked')
}
}

To test this functionality we will be using Sinon to replace the handleClick() method on our component with a “spy”..

A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. There are two types of spies: Some are anonymous functions, while others wrap methods that already exist in the system under test.
http://sinonjs.org/releases/v2.1.0/spies/
import React from 'react'
import { shallow } from 'enzyme'
import { expect } from 'chai'
import sinon from 'sinon'
import Button from '../Button'
describe('Button component', () => {
it('should call handleClick() when clicked', () => {
const spy = sinon.spy(Button.prototype, 'handleClick')
const wrapper = shallow(<Button />)
    wrapper.find('div').simulate('click')      
expect(spy.calledOnce).to.equal(true)
})
})

Above you can see that we create a spy and assign it to a method the component using the component'sprototype. Enzyme’s wrapped version of our component allows us to easily simulate a click event. The spy’s calledOnce property will now equal true allowing us to assert that the div was clicked.

Sinon lets us do some other cool things with spies like testing if a function was called with certain arguments. Their documentation is pretty good so check it out if you have some more complicated functions to test.


Mocking a component

React allows us to render components within other components..

import Book from '../Book'
class Library extends Component {
render() {
return (
<div>
<p>An example of a component containing a component</p>
<Book
author={'Some guy'}
title={'A book about something'}
genre={'Horror'} />
</div>
)
}
}

Lets imagine that this Library component is the smallest Library in the World and renders a single Book. When we test theLibrary component we don’t want to also test the Book component and its functionality, we can leave that for another test. All we want to do is test that Library contains a Book.

To do this we can use Rewire, a Babel plugin that allows you to mock components. After installing Rewire via npm you can add the plugin to your .babelrc file like so…

...
"plugins": ["rewire"],
...

And then to mock the component in your test it’s as easy as…

import React from 'react'
import { shallow } from 'enzyme'
import { expect } from 'chai'
import Library from '../Library'
const FakeBook = React.createClass({
render: () => <div>Fake Book</div>,
})
Library.__Rewire__('Book', FakeBook)
describe('Library component', () => {
it('should contain 1 Book', () => {
const wrapper = shallow(<Library />)
expect(wrapper.find(FakeBook)).to.have.length(1)
})
})

Rewire replaces the Book component withFakeBook which simply renders a div and you don’t have to worry about any props or logic that happens in the Book component.


Faking time

This is a contrived example but it represents how we might want to change state after a given amount of time using setTimeout(). After 2 seconds the text should change from ‘Running’ to ‘Done’.

class Timer extends React.Component {
constructor (props) {
super(props)
this.state = {timerStatus: 'Running'}

setTimeout( () => {
this.setState({timerStatus: 'Done'})
}, 2000)
}
  render() {
return <div>
<h1>{this.state.timerStatus}</h1>
</div>
}
}

Sinon offers a really cool API to fake time…

var clock = sinon.useFakeTimers();
Causes Sinon to replace the global setTimeout, clearTimeout, setInterval, clearInterval and Date with a custom implementation which is bound to the returned clock object.
http://sinonjs.org/releases/v2.1.0/fake-timers/

After this we call clock.tick(2000) to fake a 2 seconds in our component.

import React from 'react'
import { shallow } from 'enzyme'
import { expect } from 'chai'
import sinon from 'sinon'
import Timer from '../Timer'
describe('Timer component', () => {
it('should render "done" after 2 seconds', () => {
const wrapper = shallow(<Timer />)
clock = sinon.useFakeTimers()
clock.tick(2000)
expect(wrapper.find('h1')).to.contain.('Done')
})
})

We can then assert that the component contains ‘Done’ as expected.


These examples may be simplified for the purpose of demonstration but hopefully they give some insight in testing some common functionality in React.