Home >

Testing React Components in Rails

01 Nov 2015

As mentioned in a previous post, part of my current learning and experimentation is finding ways to mix a "traditional" Rails application with Javascript components.

Recently, I've been further exploring React, which I really like, but I couldn't find a simple way to unit test components in a Rails app using the Asset Pipeline. The benefit I'm looking for is being able to do a bulk of the frontend with server side rendered views as they are easy to build quickly and add React components for parts of the interface that need it.

Most React testing examples assume a CommonJS way of structuring and requiring Javascript files. Strangely, or maybe to increase adoption, the react-rails gem integrates with the Asset Pipeline and I wanted to find a testing solution to match.

Component

As an example I'll implement the CheckboxWithLabel component used in the Jest React tutorial.

// app/assets/javascripts/components/checkbox_with_label.js.jsx
var CheckboxWithLabel = React.createClass({

  getInitialState: function () {
    return { isChecked: false }
  },

  onChange: function () {
    this.setState({isChecked: !this.state.isChecked});
  },

  render: function () {
    var label = this.state.isChecked ? this.props.labelOn : this.props.labelOff;

    return (
      <label>
        <input
          type="checkbox"
          checked={this.state.isChecked}
          onChange={this.onChange}
        />
        {label}
      </label>
    );
  }

});

Test Setup

Rather than using Jest for our tests I'll use Jasmine, which Jest is built on but is easier to integrate with Rails using the jasmine-rails gem.

After adding jasmine-rails to the Gemfile and running the installer, rails generate jasmine_rails:install, we just need to make a couple changes.

First we need to configure react-rails to include the TestUtils addon in config/application.rb.

# config/appliction.rb
module ReactTesting
  class Application < Rails::Application
    config.react.addons = true
  end
end

To enable using the JSX preprocessor in our test we simply need to modify the spec_files matcher in spec/javascripts/support/jasmine.yml.

spec_files:
  - "**/*[Ss]pec.{js.jsx,js,jsx}"

If you want to support coffee script in your tests as well, you can use something like:

spec_files:
  - "**/*[Ss]pec.{js.jsx.coffee,js.jsx,js.coffee,js,jsx,coffee}"

Also, to force the use of PhantomJS 2.x which is already installed on my laptop we need to add:

use_phantom_gem: false

You'll also want to make sure your CI server is using PhantomJS 2.x. For Travis CI just add the following to your .travis.yml file.

before_install:
  - wget https://s3.amazonaws.com/travis-phantomjs/phantomjs-2.0.0-ubuntu-12.04.tar.bz2
  - tar -xjf phantomjs-2.0.0-ubuntu-12.04.tar.bz2
  - export PATH=$PWD:$PATH

Now on to writing our test.

Test

Our test will have two assertions. First to check the initial test is correct and a second after simulating a change.

// spec/javascripts/components/checkbox_with_label_spec.js.jsx
var TestUtils = React.addons.TestUtils

describe('CheckboxWithLabel', function () {

  it('changes the text after click', function () {
    // Render a checkbox with label in the document
    var checkbox = TestUtils.renderIntoDocument(
      <CheckboxWithLabel labelOn="On" labelOff="Off" />
    );

    var checkboxNode = ReactDOM.findDOMNode(checkbox);

    // Verify that it's Off by default
    expect(checkboxNode.textContent).toEqual('Off');

    // Simulate a click and verify that it is now On
    TestUtils.Simulate.change(
      TestUtils.findRenderedDOMComponentWithTag(
        checkbox,
        'input'
      )
    );
    expect(checkboxNode.textContent).toEqual('On');
  });

});

This matches the sample test is from the Jest tutorial. Although it should probably be noted we will not have access to Jest's Automatic Mocking feature as we are using Jasmine instead.

If we want to avoid declaring the TestUtils variable in each file we can extract it as a helper.

// spec/javascripts/helpers/react_helper.js
window.TestUtils = React.addons.TestUtils

Running Tests

Now to run tests we have two options: Rake task and browser.

Rake task

The jasmine-rails gem includes a Rake task spec:javascripts.

$ bundle exec rake spec:javascripts
Starting...

Finished
-----------------
1 spec, 0 failures in 0.023s.

ConsoleReporter finished

You can also configure this task to run as part of the default rake task.

# Rakefile
task :default => [ 'spec:javascripts' ]

Browser

Another benefit with the jasmine-rails gem is that it configures and mounts the Jasmine runner at /specs in your Rails app.

Jasmine Runner in Browser

This means using can easily stick a debugger statement in your test and use the Chrome web inspector to step through your tests.

Conclusion

Let's evaluate what we have.

Pros:

Cons:

This solution does assume you want to keep using the Asset Pipeline, but I'm guessing for most Rails apps that's the easiest way to get started with React and it follows the style of the react-rails gem.

If you'd like to see the full running code, I've created a minimal sample application at https://github.com/calebwoods/react_testing.