Skip to content

Instantly share code, notes, and snippets.

@TimothyRHuertas
Last active June 8, 2021 19:51
Show Gist options
  • Save TimothyRHuertas/d7d06313c5411fe242bb to your computer and use it in GitHub Desktop.
Save TimothyRHuertas/d7d06313c5411fe242bb to your computer and use it in GitHub Desktop.
Mocking React components with Sinon and QUnit.

React makes compositing components easy. However testing them can get ugly if you are not careful. Consider this example:

var ChildA = React.createClass({
    displayName: "ChildA",
    render: function(){
        return (<div>A in the house</div>);
    }
});

var ChildB = React.createClass({
    displayName: "ChildB",
    render: function(){
        return (<div>B in the house</div>);
    }
});


var Parent = React.createClass({
    displayName: "Parent",
    render: function(){
        return (<div>
                    <ChildA data={this.props.data.a}></ChildA>
                    <ChildB data={this.props.data.b}></ChildB>
                </div>);
    }
});

It is very tempting to write a unit test that renders Parent and asserts that the html has 2 divs. One that contains "A in the house" and another that contains "B in the house". There is a problem with this approach. What happens if ChildA's render method is altered to render "A is outta here"? Should the unit test for Parent break? No; it should not, but it will. To avoid this you need to test that Parent creates ChildA and ChildB with the correct data, instead of indirectly testing the behavior of ChildA and ChildB. Testing the behavior of ChildA and ChildB should be reserved for their respective unit tests. To do this we need a way to Mock ChildA and ChildB and intercept the properties passed to them. Ideally our test should look something like this:

QUnit.module("Test a parent component", {
    beforeEach: function () {
        this.sandbox = Sinon.sandbox.create();

        //Set up the spec helper.  Since React.createElement is static use sandbox.
        //Pass in the components you want to mock
        new ReactSpecHelper(this.sandbox).stubChildren([ChildA, ChildB]);
    },
    afterEach: function () {
        this.sandbox.restore();
    }
});

QUnit.test("It renders child A with the correct data", function(){
    var data = {a: "a", b: "b"};
    var view = TestUtils.renderIntoDocument(<Parent data={data} />);

    var childA = TestUtils.scryRenderedDOMComponentsWithClass(view,"ChildA");
    equal(childA.length, 1);
    equal(childA[0].props.data, "a");
});

QUnit.test("It renders child B with the correct data", function(){
    var data = {a: "a", b: "b"};
    var view = TestUtils.renderIntoDocument(<Parent data={data} />);

    var childA = TestUtils.scryRenderedDOMComponentsWithClass(view,"ChildB");
    equal(childA.length, 1);
    equal(childA[0].props.data, "b");
});

Attached is the utility I created to make this possible along with a sample test.

"use strict";
var ReactAddons = require("react/addons");
var TestUtils = ReactAddons.addons.TestUtils;
var React = require('react');
var Sinon = require("sinon");
var ReactSpecHelper = require("specHelpers/ReactSpecHelper");
var ChildA = React.createClass({
displayName: "ChildA",
render: function(){
return (<div>A in the house</div>);
}
});
var ChildB = React.createClass({
displayName: "ChildB",
render: function(){
return (<div>B in the house</div>);
}
});
var Parent = React.createClass({
displayName: "Parent",
render: function(){
return (<div>
<ChildA data={this.props.data.a}></ChildA>
<ChildB data={this.props.data.b}></ChildB>
</div>);
}
});
QUnit.module("Test a parent component", {
beforeEach: function () {
this.sandbox = Sinon.sandbox.create();
//Set up the spec helper. Since React.createElement is static use sandbox.
//Pass in the components you want to mock
new ReactSpecHelper(this.sandbox).stubChildren([ChildA, ChildB]);
},
afterEach: function () {
this.sandbox.restore();
}
});
QUnit.test("It renders child A with the correct data", function(){
var data = {a: "a", b: "b"};
var view = TestUtils.renderIntoDocument(<Parent data={data} />);
var childA = TestUtils.scryRenderedDOMComponentsWithClass(view,"ChildA");
equal(childA.length, 1);
equal(childA[0].props.data, "a");
});
QUnit.test("It renders child B with the correct data", function(){
var data = {a: "a", b: "b"};
var view = TestUtils.renderIntoDocument(<Parent data={data} />);
var childA = TestUtils.scryRenderedDOMComponentsWithClass(view,"ChildB");
equal(childA.length, 1);
equal(childA[0].props.data, "b");
});
"use strict";
var React = require('react');
var originalCreateElement = React.createElement;
var DivFactory = React.createFactory('div');
module.exports = function(sandbox){
return {
stubChildren: function(stubbedComponents){
sandbox.stub(React, 'createElement', function(component, props){
if(stubbedComponents.indexOf(component) === -1){
return originalCreateElement.apply(React, arguments);
}
else {
var componentFactory = React.createFactory(component);
var displayName = componentFactory().type.displayName;
if(displayName){
if(props.className){
props.className = props.className + " " + displayName;
}
else {
props.className = displayName;
}
}
return DivFactory(props);
}
});
}
};
};
@clementdubois
Copy link

Thanks for the usefull gist.
Since 0.13 "componentFactory()" will throw a warning if the passed component have required props.
I suggest to directly access "component.displayName" instead.

@pk-nb
Copy link

pk-nb commented May 4, 2016

This is fantastic—thanks so much. We modified this code into a class that registers multiple component stubs (by displayName). It gives the sinon sandbox through a global beforeEach and clears out stub and components in afterEach.

@robertleib
Copy link

@pk-nb are you able to share a gist of your refactor? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment