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.

Read more at the source