Press "Enter" to skip to content

Mock Components – from Developing a React Edge

admin

rocketborder3rev

One powerful feature in React that you learned about earlier is composing components of other components. Having one component render another one is great for modularity and code reuse, but it takes a special consideration with regard to your tests. Let’s imagine you have two components: UserBadge and UserImage, where UserBadge renders the user’s name and their UserImage. When writing tests for the UserBadge component, it is important only UserBadge functionality is being tested and that you are not implicitly testing UserImage functionality. While a co-worker might argue that testing both “is better because it’s more like real life,” you will quickly find that your tests will get harder and harder to write and maintain because they will lose focus of the “unit” you are trying to test.


LISTEN TO YOUR TESTS!

If you are concerned about your tests becoming too complex and unfocused (which you should be), then you can keep an eye out for some symptoms of this happening.


One “code smell” to watch out for is a test that requires a complex or repetitive setup process. The more painful it is to setup a test, the stronger the “smell” that there might be something suboptimal about your architecture. Often your tests are trying to give you some advice.

Here’s our plan of attack for the UserBadge test. Before rendering the UserBadge component, we should modify it so that UserImage component has been replaced by a mock component that has no real behavior. This is very dependent on the tools and architecture you have chosen for your application. For the Survey Application, we have chosen Browserify, so let’s look at that approach first. Let’s start with the basic boilerplate for a UserBadge test and a simple render call:

/** @jsx React.DOM */
var React = require("react/addons");
var TestUtils = React.addons.TestUtils;

var UserBadge = require('../../../client/testing_examples/user_badge');

describe("UserBadge", function(){
      // NOTE: This will render the actual UserImage component, 
      // which is not the desired affect
      it("should use the mock component and not the real component", function(){
        var userBadge = TestUtils.renderIntoDocument(<UserBadge />);
      });
});

You can read the full source code of our example application, a survey builder, at https://github.com/backstopmedia/bleeding-edge-sample-app.

To help us stub out the UserImage component for this test, we will use an open source module called rewireify. This module is like magic, since it allows us to rewrite local variables and local functions for a module. If you look at the source for the UserBadge module, you will see the following lines:

var UserImage = require("./user_image");
...
  render: function(){
    return (
      <div>
        <h1>{this.props.friendlyName}</h1>
        <UserImage slug={this.props.userSlug} />
      </div>
      );
  }
...

So this means that the UserBadge module has a local variable called UserImage, which is set to a react component. In the UserBadge spec, we will tell rewireify to set the UserImage local variable with a mock component. The basic approach looks like this:

var mockUserImageComponent = React.createClass({
        render: function(){
          return (<div className="fake">Fake User Image!!</div>);
        }
      });

UserBadge.__set__("UserImage", mockUserImageComponent);

Now when the UserBadge components tries to render, it will render our mockUserImageComponent and not the real UserImage component. While the above example is nice, it doesn’t account for one important side effect – test pollution. If we “__set__” a variable in one test, we need to make sure our change is reversed before the next test begins. To make sure one test doesn’t pollute another, our test should look like this:

describe("UserBadge", function(){
  describe("rewireify", function(){
    var mockUserImageComponent;

    beforeEach(function(){
      mockUserImageComponent = React.createClass({
        render: function(){
          return (<div className="fake">Fake User Image!!</div>);
        }
      });
    });

    describe("using just rewireify", function(){
      var realUserImageComponent;

      beforeEach(function(){
        // save off the real definition, so we can put it back 
        // when the test is complete
        realUserImageComponent = UserBadge.__get__("UserImage");
        UserBadge.__set__("UserImage", mockUserImageComponent);
      });

      afterEach(function(){
        UserBadge.__set__("UserImage", realUserImageComponent);
      });

      it("should use the mock component and not the real component", function(){
        var userBadge = TestUtils.renderIntoDocument(<UserBadge />);

        expect(TestUtils.findRenderedDOMComponentWithClass(userBadge, 
"fake").getDOMNode().innerHTML).toBe("Fake User Image!!");
      });
    });

  });
});

TESTUTILS.FINDRENDEREDDOMCOMPONENTWITHCLASS

We are using a utility called TestUtils.findRenderedDOMComponentWithClass in this test. We will cover the behavior of this utility later in this chapter, but for right now all you need to know is that it finds a component with the class fake.


Here is the general algorithm of the test code shown above:

  1. Define the mockUserImageComponent React component
  2. Get the value for the UserImage variable in the UserBadge module and save it in a local variable called realUserImageComponent
  3. Set the value for the UserImage variable in the UserBadge module to the mockUserImageComponent
  4. Perform the test
  5. Set the value for the UserImage variable in the UserBadge module back to the realUserImageComponent.

The good news is that this approach works great, but the bad news is that it is a lot of boilerplate code that we will probably need in almost every spec because most specs will render other components. So, this is where a custom Jasmine helper can make a large difference. What we’re going to do is write a module that has responsibilities: a function that we can call from our spec to rewire a variable, and an afterEach hook that reverses all of those changes at the end. To reverse the changes, we just keep track of all the rewire’s that we’ve done in an array and then loop through that array in the clean up phase. Here is the code for our helper module, which you write in a file called test/client/helpers/rewire-jasmine.js:

var rewires = [];
var rewireJasmine = {
  rewire: function(mod, variableName, newVariableValue){
    // save off the real value, so we can revert back to it later
    var originalVariableValue = mod.__get__(variableName);

    // keep track of everything which was rewire'd through this 
    // helper module
    rewires.push({
      mod: mod,
      variableName: variableName,
      originalVariableValue: originalVariableValue,
      newVariableValue: newVariableValue
    });

    // rewire the variable to the new value
    mod.__set__(variableName, newVariableValue);
  },

  unwireAll: function(){
    for (var i = 0; i < rewires.length; i++) {
      var mod = rewires[i].mod,
        variableName = rewires[i].variableName,
        originalVariableValue = rewires[i].originalVariableValue;

      // rewire the variable name back to the original value
      mod.__set__(variableName, originalVariableValue);
    }
  }
};

afterEach(function(){
  // unwire all modules we rewire'd
  rewireJasmine.unwireAll();

  // reset the array back to an empty state in preperation for 
  // the next spec
  rewires = [];
});

module.exports = rewireJasmine;

With this helper module in our toolbox, we can simplify our UserBadge/UserImage example to this:

var rewireJasmine = require("../helpers/rewire-jasmine");
var UserBadge = require('../../../client/testing_examples/user_badge');

describe("UserBadge", function(){
    describe("with a custom rewireify helper", function(){
      beforeEach(function(){
        rewireJasmine.rewire(UserBadge, "UserImage", mockUserImageComponent);
      });

      it("should use the mock component and not the real component", function(){
        var userBadge = TestUtils.renderIntoDocument(<UserBadge />);

        expect(TestUtils.findRenderedDOMComponentWithClass(userBadge, 
"fake").getDOMNode().innerHTML).toBe("Fake User Image!!");
      });
    });
  });
});

Look how easy that is! rewireJasmine.rewire(UserBadge, “UserImage”, mockUserImageComponent); handles the saving off of the original value and the module registers the afterEach cleanup hook.


OTHER NPM IMPLEMENTATIONS

If you are using Webpack instead of Browserify for client side code, you will want to swap out rewireify with rewire-webpack. If you are testing a Node.js application instead of client side code, you will want to use the original project rewire (the project that spawned rewireify and rewire-webpack. The interface is slightly different, but you can read more about it here.


But what if a project isn’t built with the npm/require goodies and we’re using <script> tags that store the components in a global variable? Don’t worry, you aren’t out of luck either. The pattern is exactly the same, but the code looks a bit different:

  describe("global variables", function(){
    var mockUserImageComponent, realUserImageComponent;

    beforeEach(function(){
      mockUserImageComponent = React.createClass({
        render: function(){
          return (<div className="fake">Fake Vanilla User 
 Image!!</div>);
        }
      });

      // we need to save off the real definition, so we can put it back when the test is complete
      realUserImageComponent = window.vanillaScriptApp.UserImage;
      window.vanillaScriptApp.UserImage = mockUserImageComponent;
    });

    afterEach(function(){
      window.vanillaScriptApp.UserImage = realUserImageComponent;
    });

    it("should use the mock component and not the real component", function(){
      var UserBadge = window.vanillaScriptApp.UserBadge;
      var userBadge = TestUtils.renderIntoDocument(<UserBadge />);

      expect(TestUtils.findRenderedDOMComponentWithClass(userBadge,
 "fake").getDOMNode().innerHTML).toBe("Fake Vanilla User Image!!");
    });
  });

Let’s review what we’ve learned so far:

  • How to render a component into the document
  • How to stub out a nested component with a mock implementation

flameborder_reactedge