Setting Up Your Rails 5 App For JavaScript Testing

Ruby bracelet

About 2 years ago I wrote about how to set up your Rails app for JavaScript testing, so I figured it was time for an update. It’s pretty much the same as the previous post, but it assumes you’re using Rails 5 and newer versions of Teaspoon and Magic Lamp.

And away we go!

Setting up Teaspoon

Teaspoon is JavaScript test runner for Rails that runs in continuous integration (CI) and the browser for easy debugging. You’ll have to decide whether to Jasmine, Mocha, or QUnit  as your actual JavaScript test framework (When it’s up to me, I go with Mocha). We want to add the Teaspoon gem to the test and development groups in our Gemfile:

group :development, :test do
  gem "teaspoon-mocha" # or "teaspoon-jasmine" or "teaspoon-qunit"
end

After you bundle install you’ll want to run:

$ rails generate teaspoon:install

That command will generate spec/teaspoon_env.rb and spec/javascripts/spec_helper.js files; both contain a ton of really helpful comments  that explain how to configure Teaspoon in all kinds of ways. For now, let’s just make sure we have all of the JavaScript we need in our spec/javascripts/spec_helper.js.

Teaspoon comes with some handy JavaScript testing libraries like Expect, Sinon, and Chai we can pull in using Sprockets comments/directives. Since we’re using Mocha we’ll need to pull in an assertion library (Chai tends to be my go to) which means fiddling with some of the comments:

// Helpful comments...
//= require support/chai 
// More helpful comments...
window.expect = chai.expect;

Note: Make sure you didn’t delete //= require application or else our code won’t be loaded into the test environment!

Now we can get our first tests going by adding a file called spec/javascripts/test_spec.js with the following:

describe("Testing", function() {
  it("is going so smoothly", function() {
    expect(true).to.equal(true);
  });

  it("is not going so smoothly", function() {
    expect(true).to.equal(false);
  });
});

Now if we run rake teaspoon or simply teaspoon from the root of our Rails app we should see:

Starting the Teaspoon server...
Puma starting in single mode...
* Version 3.6.0 (ruby 2.3.1-p112), codename: Sleepy Sunday Serenity
* Min threads: 5, max threads: 5
* Environment: test
* Listening on tcp://127.0.0.1:51170
Use Ctrl-C to stop
Teaspoon running default suite at http://127.0.0.1:51170/teaspoon/default
.F

Failures:

 1) Testing is not going so smoothly
    Failure/Error: expected true to equal false

Finished in 0.01300 seconds
2 examples, 1 failure

Failed examples:

teaspoon -s default --filter="Testing is not going so smoothly"

Bam! And if we wanted to debug our failing test, we can simply run rails server and visit localhost:3000/teaspoon/default and watch our tests run. Now you can crack open Chrome developer tools, play with the console, set break points and all of that good stuff. Testing that true is true is all well and good, but we’re writing JavaScript to manipulate the DOM, so let’s get some HTML in there. You could put some fixture files at spec/javascripts/fixtures and load them via Teaspoon, but we don’t want to have to make sure our fixture files are always up to date (if you really want to, you can read more about that here). Instead we’re going to make sure our fixtures’ markup is always up to date with Magic Lamp.

What is Magic Lamp?

Magic Lamp provides an easy way to get your Rails templates as well as arbitrary strings and JSON into your JavaScript tests. By testing your JavaScript against your actual templates and partials, your tests will break when your markup changes in a way that breaks your code. This beats the alternatives of creating tons of fixture files or stubbing out your markup with jasmine-fixture. It’s also great if you want to test your views with jQuery instead of assert_select (gross). Keep reading to see an example of Magic Lamp in action (or check out the readme).

Setting up Magic Lamp

First we’ll add Magic Lamp to our test and development groups in our Gemfile:

group :development, :test do
  gem "teaspoon-mocha"
  gem "magic_lamp" # just added
end

Then after we bundle install we need to mount Magic Lamp in our config/routes.rb

Rails.application.routes.draw do
  mount MagicLamp::Genie, at: "/magic_lamp" if defined?(MagicLamp)
  # ...
end

Be sure to add it to the top of draw block if you have any catchall routes that might catch requests to /magic_lamp first. Next you’ll want to add //= require magic_lamp to your spec/javascripts/spec_helper.js. It should be similar to:

// Lots of helpful comments...
//= require application
//= require support/chai
//= require magic_lamp
window.expect = chai.expect;

Now Magic Lamp is ready to go. So let’s say you have this scaffolded up form in your app:

<%= form_for(order) do |f| %>
 <% if order.errors.any? %>
   <div id="error_explanation">
     <h2><%= pluralize(order.errors.count, "error") %> prohibited this order from being saved:</h2>

     <ul>
       <% order.errors.full_messages.each do |message| %>
         <li><%= message %></li>
       <% end %>
     </ul>
   </div>
 <% end %>

 <div class="actions">
   <%= f.submit %>
 </div>
<% end %>

And you have some JavaScript that will make the error messages fade out that you want to test. Create a file called magic_lamp.rb in spec/javascripts that contains:

MagicLamp.fixture do
  order = Order.new(name: "invalid name")
  order.valid?
  render partial: "orders/form", locals: { order: order }
end

Then in your JavaScript tests you can add:

describe('Order form errors', function() {
  beforeEach(function() {
    MagicLamp.load("orders/form");
  });

  it('depends on the dom', function() {
    expect($('#error_explanation').length).to.equal(1);
  });
});

And the html will be on the page. Now if you change that div‘s id from error_explanation to errors the tests will break. Also, if your template depends on instance variables, you can just assign them in the Magic Lamp fixture block just like if you were in a Rails controller.

Summary

Those are the main pieces you need to get your Rails app’s JavaScript and templates ready for some serious testing. Teaspoon for running your JavaScript specs in the browser, on the command line and in CI. Magic Lamp for getting your templates and partials into your JavaScript specs so your markup changes break the tests for your dependent code and less assert_select when testing views. You can find out more about Teaspoon and Magic Lamp below (or at your local library, provided you check out this blog post there and click on the links below), along with some other helpful JavaScript testing tools.

  • Teaspoon: a JavaScript test runner for Rails (see above)
  • Magic Lamp: a way to get your Rails templates into your JavaScript specs (also see above)
  • Mocha: a JavaScript bdd testing framework
  • Sinon: a JavaScript library that provides helpful stubs and spies and can mock network requests
  • Chai: a highly pluggable JavaScript test assertion library
  • Chai plugin directory
  • sinon-chai: adds some helpful assertions for Sinon spies and stubs
  • chai-fuzzy: allows you to assert that a JavaScript array or object is like another (since === is very strict and == can’t be trusted)
  • chai-jquery: adds some helpful assertions for jQuery objects
Michael is a member of DevMynd’s software engineering team focusing on mobile apps and web development. He has been with the company since 2013.