In Rails 3.1, CoffeeScript was introduced as the default language for client side interactions (and with vociferous discussion from all sides). I was very excited by the choice made by the Rails team and have been gnashing at the teeth to get my hands on a project with Rails and CoffeeScript working in gorgeous harmony. What I found was that for production code Rails and CoffeeScript work together swimmingly, but the development story is, at best, incomplete.

Writing a feature in CoffeeScript

A thought experiment: let's walk through the development of a feature. Let us try GitHub's "Command-T" functionality to search for files. The feature calls for the following functionality:

  • When a user presses 't' in her repository view, show the repository search view.
  • In the repository search view, the user is allowed to search for files by just typing in any part of the desired filename.
  • After a short delay, the search is executed and the results are displayed.
  • The user can then use the up and down arrow keys to browse the results.
  • When the user hits enter/return, she is then taken to the selected result's page.

I am leaving out many parts of this feature, but this high level overview is good enough to get use thinking about the complexity involved. From a technical perspective, we know several things: we need to respond to user events (keyboard), we need to asynchronously fetch and display results, we need to know enough about the results to act upon them and that we are pretty much stuck with client side JavaScript/CoffeeScript if we want this to be a good user experience. This is a complex peice of code that cannot be easily written. There is going to need to be spikes, research and, hopefully, tests that surround it.

How might we do this in Rails? Well, the first requirement is easy enough; it is a one liner in CoffeeScript. We could even add a Cucumber on Selenium test that verifies the hiding and showing of the correct panels.

The next part starts to get complicated, though. We need to be able to capture all of the user's keyboard interactions, decide if it is alphanumeric, save the key somewhere and then display it. Depending on how naive we are implementing this, we are talking about a significant function with many branches. We could probably get through the pain of writing Cucumber on Selenium tests for this particular part but we can already see that this is not going to be sustainable. If just one part of this breaks, then our Selenium tests are only going to be able to tell us as much as a user could: "It's broken. I don't know why but it just breaks!"

Unit Testing

The obvious part that I left out is Unit Testing. As far as CoffeeScript goes, Rails is test framework free. It has no opinions on how you should be writing these tests and no guiding light. We have to turn to external tools like Jasmine or QUnit. Both of these tools work by running tests in a browser. That is, you need to open up a browser in order to run your Javascript tests. In my opinion, that is insane. These tools barely provide an isolated environment and, by themselves, do not work in "headless" mode. In order to get them to work in CI, you actually need to fire up Selenium, open the browser to the test run page and check for a span tag with success class.

We have to dig a little deeper into the rabbit hole to get Headless Javascript testing: we need to use tools like PhantomJS or jasmine-headless-webkit. These tools are light years ahead of the Selenium method of automation but are still a bit distasteful. We are still running tests by loading an HTML page even though it is in a "headless" browser. We are even still checking for a span tag with success class.

Even with these tools, though, the development experience is bad with CoffeeScript. Most people will actually compile the coffeescript down to Javascript before running it in Jasmine. You still need to open up/reload a page in order to run tests. The experience of running tests locally and running them in CI is completely different. There is no CLI experience that matches your "normal" development experience. The tests will often run slow because of browser launching, html rendering, etc. The tests will often not be "units" because developers will write tests that interact with the DOM. When adding a test, you have to add the test to the script include blocks in your HTML file because the browser cannot access the filesystem to look for whole folders.

A Better Solution

For unit testing, I propose that we focus away from browser-based testing frameworks and focus on native Javascript interpreters that are not based in a web browser. As a proof of concept, I have gotten a spec library that I wrote for CoffeeScript on top of Nodejs to work with a Rails application. Here is a ridiculously simple example:

# app/assets/javascripts/user.coffee
root = (exports ? this)

class root.User
  setName: (value) ->
    @name = value
# spec/user_spec.coffee

describe "User", ->
    spec ->
        user = new User()
        user.setName "Bob"
        user.name.should equal "Bob"

Again, this is ridiculously simple and what I've done is a proof of concept. I plan to do some more work on this specific solution but I'm hoping that this is just an inspiration for more work in making the CoffeeScript inside of Rails story much, much better.

Not the Best Solution

My solution above has several flaws that I'd like to point out (and if someone has a resolution for it, please let me know!).

  • CSpec is very, very incomplete. This could rapidly change but please be aware I'm not necessarily advocating this for real use today.
  • CSpec requires NodeJS to be installed. This is yet another dependency that a developer must install in order to get up and running. Looking at other solutions like therubyracer is something that I would like to do.
  • CSpec requires that you "publish" your classes via the exports module, if it exists. It's a small but hacky fix to get at classes defined in other files.
  • CSpec does not have a browser. That means that JQuery is not supported. You'll have to decouple your logic from JQuery (a good thing) but you will still need to do JQuery testing through Selenium and/or Jasmine.
  • CSpec is not well integrated with Rails. This is something that could be quickly resolved with the appropriate generators and other railties in a cspec-rails gem.

Again, while I do hope to improve upon my solution what I really hope is that the Rails community can improve upon the development flow of CoffeeScript. I think we can and should encourage a close relationship between Rails and CoffeeScript and that we should have the same high standards for both.

Discuss this post at Hacker News