Test a React Component function with Jest Test a React Component function with Jest javascript javascript

Test a React Component function with Jest


It looks like you're on the right track. Just to make sure everyone's on the same page for this answer, let's get some terminology out of the way.

Mock: A function with behavior controlled by the unit test. You usually swap out real functions on some object with a mock function to ensure that the mock function is correctly called. Jest provides mocks for every function on a module automatically unless you call jest.dontMock on that module's name.

Component Class: This is the thing returned by React.createClass. You use it to create component instances (it's more complicated than that, but this suffices for our purposes).

Component Instance: An actual rendered instance of a component class. This is what you'd get after calling TestUtils.renderIntoDocument or many of the other TestUtils functions.


In your updated example from your question, you're generating mocks and attaching them to the component class instead of an instance of the component. In addition, you only want to mock out functions that you want to monitor or otherwise change; for example, you mock _onChange, but you don't really want to, because you want it to behave normally—it's only refresh that you want to mock.

Here is a proposed set of tests I wrote for this component; comments are inline, so post a comment if you have any questions. The full, working source for this example and test suite is at https://github.com/BinaryMuse/so-jest-react-mock-example/tree/master; you should be able to clone it and run it with no problems. Note that I had to make some minor guesses and changes to the component as not all the referenced modules were in your original question.

/** @jsx React.DOM */jest.dontMock('../indicator');// any other modules `../indicator` uses that shouldn't// be mocked should also be passed to `jest.dontMock`var React, IndicatorComponent, Indicator, TestUtils;describe('Indicator', function() {  beforeEach(function() {    React = require('react/addons');    TestUtils = React.addons.TestUtils;    // Notice this is the Indicator *class*...    IndicatorComponent = require('../indicator.js');    // ...and this is an Indicator *instance* (rendered into the DOM).    Indicator = TestUtils.renderIntoDocument(<IndicatorComponent />);    // Jest will mock the functions on this module automatically for us.    TriggerAnAction = require('../action');  });  it('waits 1 second foreach tick', function() {    // Replace the `refresh` method on our component instance    // with a mock that we can use to make sure it was called.    // The mock function will not actually do anything by default.    Indicator.refresh = jest.genMockFunction();    // Manually call the real `_onChange`, which is supposed to set some    // state and start the interval for `refresh` on a 1000ms interval.    Indicator._onChange();    expect(Indicator.state.elapsed).toBe(30);    expect(setInterval.mock.calls.length).toBe(1);    expect(setInterval.mock.calls[0][1]).toBe(1000);    // Now we make sure `refresh` hasn't been called yet.    expect(Indicator.refresh).not.toBeCalled();    // However, we do expect it to be called on the next interval tick.    jest.runOnlyPendingTimers();    expect(Indicator.refresh).toBeCalled();  });  it('decrements elapsed by one each time refresh is called', function() {    // We've already determined that `refresh` gets called correctly; now    // let's make sure it does the right thing.    Indicator._onChange();    expect(Indicator.state.elapsed).toBe(30);    Indicator.refresh();    expect(Indicator.state.elapsed).toBe(29);    Indicator.refresh();    expect(Indicator.state.elapsed).toBe(28);  });  it('calls TriggerAnAction when elapsed reaches zero', function() {    Indicator.setState({elapsed: 1});    Indicator.refresh();    // We can use `toBeCalled` here because Jest automatically mocks any    // modules you don't call `dontMock` on.    expect(TriggerAnAction).toBeCalled();  });});


I think I understand what you're asking, at least part of it!

Starting with the error, the reason you are seeing that is because you have instructed jest to not mock the Indicator module so all the internals are as you have written them. If you want to test that particular function is called, I'd suggest you create a mock function and use that instead...

var React = require('react/addons');var Indicator = require('../Indicator.js');var TestUtils = React.addons.TestUtils;var refresh = jest.genMockFunction();Indicator.refresh = refresh; // this gives you a mock function to query

The next thing to note is you are actually re-assigning the Indicator variable in your example code so for proper behaviour I'd rename the second variable (like below)

var indicatorComp = TestUtils.renderIntoDocument(<Indicator />);

Finally, if you want to test something that changes over time, use the TestUtils features around timer manipulation (http://facebook.github.io/jest/docs/timer-mocks.html). In your case I think you can do:

jest.runAllTimers();expect(refresh).toBeCalled();

Alternatively, and perhaps a little less fussy is to rely on the mock implementations of setTimeout and setInterval to reason about your component:

expect(setInterval.mock.calls.length).toBe(1);expect(setInterval.mock.calls[0][1]).toBe(1000);

One other thing, for any of the above changes to work, I think you'll need to manually trigger the onChange method as your component will initially be working with a mocked version of your Store so no change events will occur. You'll also need to make sure that you've set jest to ignore the react modules otherwise they will be automatically mocked too.

Full proposed test

jest.dontMock('../Indicator');describe('Indicator', function() {  it('waits 1 second for each tick', function() {    var React = require('react/addons');    var TestUtils = React.addons.TestUtils;    var Indicator = require('../Indicator.js');    var refresh = jest.genMockFunction();    Indicator.refresh = refresh;    // trigger the store change event somehow    expect(setInterval.mock.calls.length).toBe(1);    expect(setInterval.mock.calls[0][1]).toBe(1000);  });});