February 20, 2015
At Contactually, our app's back-end is written in Rails 4, while most of the front-end is a Single-Page Application (SPA) written in JavaScript, using Backbone.js, Marionette.js, and a number of JavaScript plugins.
On the back-end, we have a suite of RSpec unit specs and Capybara acceptance specs to make sure everything works on the back-end. When I joined Contactually in September 2014, the front-end code didn't have any unit tests - everything was tested manually by traditional QA processes.
In our app, there's a lot of business logic between different objects.
User
has many Contact
s and Grouping
sContact
can be in many Grouping
s, but only certain subtypes of Grouping
, such as Bucket
sGrouping
can belong to many User
sDomain
can have many User
s, and many Grouping
sAlmost all of this logic is spec'ed out and verified by RSpec and Capybara specs, but a lot of the same business logic on the client-side was not, which led to hard-to-find bugs in the Backbone app. As an example, after creating a new Contact
, the UI didn't update properly despite the new Contact
was created successfully on the back-end.
I set up a Test-Driven Development (TDD) environment for testing the various Views, Models, Controllers, and Mixins that make up Contactually's Backbone app. Going forward with new UI features or bug fixes, any developer can set up a JavaScript spec with minimal overhead and keep the TDD ball rolling.
Since Contactually is a Rails app, I chose a JS testing framework that's similar in style to RSpec. I chose Mocha (and Chai) over alternatives such as QUnit or Mocha because of much simpler setup and configuration and much easier asynchronous testing support. Check out this link for a more detailed comparison between Mocha, QUnit and Jasmine.
Konacha is a Rails engine for testing JavaScript in a Rails app. It provides the Mocha test framework and Chai assertion as defaults, but you can plug in other frameworks. For the purposes of this tutorial, we'll stick with Mocha and Chai.
Rails Assets is a gem source to install front-end dependencies, such as Underscore, jQuery, Backbone, and the like, by specifying them in the app's Gemfile
. We'll use rails-assets
gems to install the necessary front-end dependencies for this tutorial.
Instead of using Backbone's built-in template engine with Underscore, we'll use Handlebars. The handlebars_assets
gem has the most recent stable version of the Handlebars JS library and will allow the Rails app to precompile any Handlebars template files (ending in .hbs
) for use by Backbone or Marionette views.
Poltergeist is a webdriver that both Capybara and Konacha can use to run their respective specs in PhantomJS. It's the preferred webdriver for Konacha, so we're including it in the tutorial.
This tutorial assumes that you have some experience with setting up and running a Rails app, and some JavaScript development experience. This tutorial will walk you through setting up a Rails app with the konacha
, rails-assets
, and handlebars_assets
gems, setting up the JavaScript testing environment, and writing some JavaScript modules and Mocha specs.
You can also get the tutorial's full app on Github.
We'll build a simple todo app, where each todo is called a Dot
, and a list of todos is called a Dots
.
For this tutorial, we'll be using the following tools and their respective versions:
* ruby 2.1.5
* rails 4.2.0
* phantomjs 1.9.7
gem install rails -v 4.2.0
rails new todot
cd todot
git init .
git commit -am "initial commit"
Create a spec/javascripts
folder in the root of the app folder. Here is where you'll keep all your JS spec files. All spec files end in _spec.js
(or _spec.coffee
if you're using CoffeeScript.)
To configure Mocha when running the JS specs using Konacha, create a spec_helper.js
file, similar to the spec_helper.rb
file for Ruby RSpec.
We'll use the following configuration for the todot
app.
mocha.ui('bdd');
// ignore the following globals during leak detection
mocha.globals(['Backbone', 'JST']);
// Show stack trace on failing assertion.
chai.config.includeStack = true;
ENV = {
TESTING: true
};
beforeEach(function() {
window.SANDBOX = $("#konacha");
});
The SANDBOX
is a div
that Konacha sets up in an iframe
for each spec. We'll use the SANDBOX
for one of our Marionette View specs, dots_view_spec.js
.
Create an initializer for Konacha at config/initializers/konacha.rb
with the following:
if defined?(Konacha)
Konacha.configure do |config|
config.spec_dir = 'spec/javascripts'
config.stylesheets = %w{application}
config.javascripts = %w(chai konacha konacha/iframe)
config.driver = :poltergeist
end
end
When running Konacha's browser test environment, each spec will be run in its own separate iframe inside the browser. Rather than each iframe have its own copy of all the JavaScript libraries required to run its spec, we can use a single JavaScript manifest file with all the require
s we need, and each iframe will use this file. This can significantly speed up when running Konacha.
Create app/assets/javascripts/konacha/index.js
with the following:
//= require jquery
//= require underscore
//= require backbone
//= require backbone.marionette
//= require handlebars.runtime
//= require handlebars-helpers/src/helpers.js
In config/initializers/konacha.rb
, the konacha
item in the line config.javascripts = %w(chai konacha konacha/iframe)
refers to the above manifest file.
By default, the handlebars_assets
gem will look for HandlebarsTemplates
in any JavaScript files that will use a pre-compiled template. For sake of brevity in our code, we'll use the token JST
instead.
Create the following at config/initializers/handlebars_assets.rb
:
if defined?(HandlebarsAssets)
HandlebarsAssets::Config.template_namespace = 'JST'
end
Let's say you have a Handlebars template, app/assets/javascripts/templates/dots/dot_view.hbs
. To use this template in a Marionette view:
var DotView = Backbone.Marionette.ItemView.extend({
template: JST['dots/dot_view'],
model: Dot,
className: 'dot-view',
...
});
This gem has a set of simple and common Handlebars helpers that we can use in our Handlebars templates. Its gem is included in the Gemfile
in the repo and its JavaScript file is included in app/assets/javascripts/konacha/index.js
.
konacha
You can run your JavaScript specs in Konacha one of two ways: either in the browser, or on the command-line using PhantomJS.
$ bundle exec rake konacha:run # at the command line
$ bundle exec rake konacha:serve # in the browser at http://localhost:3500
You can configure Konacha to run on a different port in the browser, such as 9001
, by setting config.port = 9001
, in config/initializers/konacha.rb
. The same applies for configuring the port for running via the command line: set it using config.runner_port
.
dot.js
and dot_spec.js
Each Dot
in the app will have the following:
* a name
* a priority
- represented as an integer
* a status
- either 'new'
or 'complete'
Here's our Dot
Model at app/assets/javascripts/models/dot.js
:
var Dot = Backbone.Model.extend({
defaults: {
name: 'Dot',
priority: 1,
status: 'new'
},
mark_as_complete: function() {
this._mark_as('complete');
},
mark_as_new: function() {
this._mark_as('new');
},
_mark_as: function(status) {
this.set('status', status);
}
});
Here's its spec at spec/javascripts/models/dot_spec.js
:
//= require spec_helper
//= require models/dot
describe('Dot', function() {
var subject;
beforeEach(function() {
subject = new Dot();
});
it('has the correct defaults', function() {
expect(subject.get('name')).to.eq('Dot');
expect(subject.get('priority')).to.eq(1);
expect(subject.get('status')).to.eq('new');
});
it('can update its status', function() {
subject.mark_as_complete();
expect(subject.get('status')).to.eq('complete');
subject.mark_as_new();
expect(subject.get('status')).to.eq('new');
});
});
Run bundle exec rake konacha:serve
and go to https://localhost:3500/models/dot_spec?grep=Dot
. Click on the little arrows next to each spec's line to show the actual JavaScript code for that spec.
Check out the tutorial's repository for the rest of the todot
JavaScript modules and specs, and get rolling with JavaScript TDD in Rails 4 & Konacha!