With Web applications becoming increasingly complicated it’s a good idea to write automated tests for JavaScript code. Jasmine is a nice tool for doing this. It has an interface similar to RSpec’s with describe and it calls for defining the JavaScript’s behaviour. In this episode we’ll use Jasmine to test-drive a new JavaScript feature in a Rails application.

Test-Driving a Credit Card Form

The application we’ll be working with is an order form. The user can enter a credit card number in this form and when they do so we want to have some client-side validation to check the number using a mod 10 algorithm.

We want to be sure that this code is correct so we’ll add this feature using test-driven development with Jasmine. There are many Ruby gems available for integrating Jasmine into Rails; the one we’ll use is called Jasminerice. This has excellent support for the Rails asset pipeline and has jasmine-jquery built-in. To set it up we need to add its gem to the development and test groups in our application and then run bundle to install it.

/Gemfile

group :development, :testdo
gem 'jasminerice'end

Next we’ll make a new spec/javascripts directory in our application.

terminal

$ mkdir -p spec/javascripts

RSpec also uses the spec directory but this completely independent of that and Jasmine doesn’t require RSpec. In this directory we’ll create a spec.js.coffee file. This will be the central file and here we can require any other JavaScript files that we want to use in our specs. We do this using Sprockets so it works the same way as the Rails asset pipeline.

We could require the whole application’s JavaScript in this file but it can be better to pick and choose exactly what we want to require so that we can focus on what’s needed inside our specs. For now we’ll include the jquery file and use require_tree to include all the other specs we’ll write.

/spec/javascripts/spec.js.coffee

#= require jquery
#= require_tree .

Our First Spec

We’ll write the specs for our credit card validation behaviour in a new file called credit_card_spec.js.coffee. It’s not essential to use CoffeeScript but the specs look much nicer written this way than they do written in JavaScript. The first test we’ll write checks that the JavaScript code strips out any spaces and dashes from the entered number.

If our Rails app is running we can run the specs by visiting the /jasmine path.

This shows that our one spec is failing as it can’t find the CreditCard class which is to be expected as we haven’t yet defined it. We’ll define the class in the app/assets/javascripts directory.

/app/assets/javascripts/credit_card.js.coffee

class CreditCard

We’ll need to make this class available to our specs by adding it to the spec file.

/spec/javascripts/spec.js.coffee

#= require jquery
#= require credit_card
#= require_tree .

When we reload the specs page we see the same error. This is because CoffeeScript
wraps each file within a scope so that variables defined in a file are not available globally. To remedy this we can set our CreditCard class to a global variable.

/app/assets/javascripts/credit_card.js.coffee

class CreditCard
@CreditCard = CreditCard

Another way to do this is to put an at sign in front of the class name, but this technique doesn’t appear to be used much so we’ll stick with what we have. When we reload our specs now we get a different error message: Expected undefined to be '123'. This is expected as we aren’t saving the number in the class. To fix this we’ll add a constructor that takes a number and which sets an instance variable set to that number with any spaces or dashes removed.

When we reload the specs now the error is Expected '12-3' to be '123'. The specs have already found a bug in our code. By default JavaScript’s replace function will only replace the first match it finds; we’ll need to alter our regular expression to replace all matches.

This is a typical workflow in Jasmine. If we look at the site’s documentation we’ll see that it includes actual executable specs which show us the different matchers. All these specs are executed at the bottom of the page to show that they all pass with Jasmine.

Adding Mod 10 Validation

We still have more to do in our application so we’ll add some more specs to define our CreditCard class. Next we’ll add a spec to test the mod 10 validation.

Even though most of the logic is implemented now we still need to present this behaviour to the user when they enter their credit card number. We can do this with some jQuery code but how can we test-drive this behaviour? The jasmine-jquery project can help here. It allows us to define HTML fixture files which we can load in to specs and provides matchers that we can use to check the jQuery behaviour. This is included with jasminerice so we don’t need to install anything else to use it. Instead we can jump right in and start creating fixture files. We’ll create a fixtures directory under /spec/javascripts and create an order_form.html file in it. This file should contain some code that simulates the HTML produced by our application. We’ll add a simple form with a text field for the card number and a div to display any errors.

Here we load the fixture field, find the card number text box, enter an invalid credit card number into it, and then call blur() on it to simulate the user moving on to the next field. When this happens we expect the error div to contain the text “Invalid card number.”. We have to add one more step to this process as the jQuery code that fires when the text box isn’t loaded. It needs to be attached to the fixture that’s loaded in the spec. Normally when testing jQuery it’s easiest to make a jQuery plugin so that we can inject the functionality after the fixture has loaded. If our text field had a validateCreditCardNumber function that jQuery functionality would be added when this code runs.

This code loops through all the matching elements and listens to each one’s blur event. When this event fires a new CreditCard instance is created and the text field’s value is validated. If the validation fails an error message is displayed. When we run the specs again now they all pass.

We can use our new jQuery plugin to add this behaviour to our order form. All of our tests are currently passing, however, so how do we ensure that our plugin is actually working on the page? The Jasmine specs we’ve been writing so far aren’t particularly good for testing acceptance-level behaviour and we’d be better using Capybara with a JavaScript driver such as Selenium or Headless Webkit. There’s more detail on how to do this in episode 257. We’ll carry on and add this behaviour as if we had some acceptance tests in place. First we’ll modify our form and add a span element so that the error messages have a place to be displayed.

In our orders.js.coffee file we can now add the plugin’s behaviour to the credit card number field when the DOM has loaded.

/app/assets/javascripts/orders.js.coffee

jQuery ->
$('#order_card_number').validateCreditCardNumber()

We can now try this our in the browser. If we reload the page and enter an invalid card number we should see an error message when we move out of that text field.

This works, but when we go back to that text field, enter a valid card number then tab out of that field again the error message remains. We have a bug in our jQuery plugin but before we fix it we’ll duplicate it in a failing spec.

Our specs all pass again now and if we try it in the browser it now works as expected too. There’s more we could do here such as cancelling the form submission if the number is invalid but we have a good start.

Running Tests Automatically With Guard::Jasmine

We’ll finish off this episode by showing the Guard::Jasmine gem. This gives us a great way to run our specs continuously as we work. Guard::Jasmine works headlessly and relies on Phantom.JS so we’ll need to install this first. If you’re running Mac OS X the easiest way to do this is to install it through Homebrew.

terminal

$ brew install phantomjs

In our application’s gemfile we can now add then gem then run bundle to install it.

/Gemfile

group :development, :testdo
gem 'jasminerice'
gem 'guard-jasmine'end

To set up the guardfile we can run this command:

terminal

$ guard init jasmine

Now when we run guard it will monitor the spec/javascript directory for changes.