Opinionated Programmer - Jo Liss's musings on enlightened software development.

Getting Started With Konacha: JavaScript Testing on Rails

Konacha is a testing tool for JavaScript applications running on Rails.

Why Konacha?

  • It’s very fast.
  • It treats JavaScript like a first-class citizen: Your tests are written in JavaScript, call into JavaScript code, and inspect JavaScript objects. You can still trigger events, e.g. with jQuery, if you need to simulate user actions.
  • It comes with support for the Rails asset pipeline. [1]
  • It supports CoffeeScript.
  • You can use the in-browser runner (good for development), or run your test suite from the command line through Selenium (good for build servers).

What it cannot do is talk to the server. In particular:

  • It cannot use the database.
  • It cannot access your Rails (server-side) views. Any DOM nodes you are testing need to be created on the client side (for instance with JST templates), not served out by views.

When you need either of these two, write integration tests with Capybara instead.

Tutorial: Overview

Testing Plain JavaScript

Let’s start by testing some pure JavaScript (without DOM manipulation). Say you want to test the following function in a Rails 3.1+ app:

app/assets/javascripts/prime_numbers.js
1
2
3
4
5
6
7
8
isPrime = function(n) {
  for (var i = 2; i < n; i++) {
    if (!(n % i)) {
      return false;
    }
  }
  return true; // intentionally wrong for 0 and 1
};

To test this, first add Konacha to your Gemfile:

Gemfile
1
2
3
group :test, :development do
  gem 'konacha'
end

Now mkdir -p spec/javascripts, and create a spec file:

spec/javascripts/prime_numbers_spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//= require prime_numbers

describe('isPrime', function() {
  it('should be true for prime numbers', function() {
    isPrime(2).should.be.true;
    isPrime(3).should.be.true;
  });
  it('should be false for numbers with non-trivial divisors', function() {
    isPrime(4).should.be.false;
    isPrime(6).should.be.false;
  });
  it('should be false for 0 and 1', function() {
    isPrime(0).should.be.false;
    isPrime(1).should.be.false;
  });
});

We’ll get to the details of the syntax in a minute.

If you prefer CoffeeScript, you can also use .js.coffee spec files. For spec files in particular, I find CoffeeScript much more readable than plain JavaScript.

Run bundle exec rake konacha:serve, and point your browser at localhost:3500.

If everything loaded alright, you should now see two tests passing, and the third one failing:

Konacha isPrime test screenshot

If that’s not what you are getting, make sure you have a JavaScript console open in the Konacha browser window, so you can see errors. If there are any issues with loading your tests (such as syntax errors in your test declarations), Konacha will not catch it and display an error. You just get a blank test page, or worse, parts of your test suite are silently dropped. For that reason, I always run my tests with a JavaScript console open.

Testing the DOM

Let’s write up a minimal three-line app:

app/assets/javascripts/views.js
1
2
3
4
5
//= require jquery

appendTo = function(applicationRoot) {
  return $(applicationRoot).append('<div class="hello-world">Hello, world!</div>');
};

Here, appendTo shall be the method used to initialize our app and render it into the DOM – normally into an empty root div provided by the view. To test it with Konacha, we render it into the special #konacha div, which is automatically cleared between test cases.

spec/javascripts/view_spec.js
1
2
3
4
5
6
7
8
9
10
11
12
//= require views
//= require jquery

describe('application view', function() {
  it('should render', function() {
    // Call the production code
    appendTo('#konacha');
    // Test that "Hello World" was rendered (by testing that the
    // number of .hello-world divs is truthy)
    assert.ok($('#konacha').find('div.hello-world').length);
  });
});

Open localhost:3500, or localhost:3500/view_spec to run the test in isolation.

Writing Tests

Assertions

The assertions you saw in the test code above – .should.be.false and assert.ok(...) – are provided through the Chai testing library. Chai comes with two assertion styles, which you can mix and match freely in your tests: should/expect (“BDD-style”) and assert (“TDD-style”).

I generally find the should style more readable, but I recommend you still acquaint yourself with both styles, as they are not one-to-one equivalents.

If you are coming from RSpec, you will be disappointed to find that Chai’s should interface is much less powerful than RSpec’s. For example, it does not allow you to call arbitrary operators or predicate methods. So whereas in Ruby I would write a.should be_open, in JavaScript I write assert.ok(a.open()) or a.open().should.be.ok.

Structure

Konacha uses the Mocha framework with the BDD interface style to structure your tests into suites and test cases. The describe/it syntax should look familiar to most:

1
2
3
4
5
6
7
8
describe('Widget', function() {
  beforeEach(function() {
    // ... setup code here ...
  });
  it('should do things', function() {
    // ... test code here ...
  });
});

Practical Hints

  • Extract common code into a spec/javascripts/spec_helper.js file, and //= require spec_helper at the top of each spec file.

  • Group your tests into subdirectories as you see fit.

  • It’s happened to me several times that I wrote assertions that cannot fail. Examples include .should.be.thisIsATypo (does nothing) and $('.foo').should.not.be.empty (empty does not play with jQuery). For that reason, I recommend practicing test-first development, so you’ve seen each of your tests fail at least once.

  • Writing DOM tests with jQuery turns out to be rather awkward. Try adding chai-jquery to your project for some jQuery-specific Chai assertions. For instance:

1
2
assert.ok($('.hello-world').length); // without chai-jquery
$('.hello-world').should.exist;      // with chai-jquery
  • Always keep a JavaScript console open while running tests, so you’ll see load errors.

Further Reading

Footnotes

[1] Without asset pipeline support, you would either have to enumerate all your dependencies manually, or include the generated application.js file and live with stack-traces like application.js:54029.

Thanks to John Firebaugh, Yuri Gadow, and Joel Parker Henderson for reading drafts of this.