Recently, Iâve been doing a considerable amount of CoffeeScript work. One problem I ran into early-on was testing: I didnât want to manually convert my CoffeeScript to JavaScript before I could test it. Instead, I wanted to test from CoffeeScript directly. Howâd I end up doing it? Read on to find out! April of 2012 --- Youâll need to have Node.js and Node Package Manager installed. Before we continue on, Iâll point out that you need to have a decent knowledge of CoffeeScript for this tutorial; I wonât be explaining the bits and pieces here. If youâre interested in CoffeeScript, you should check out the CoffeeScript tuts available here on Nettuts+, or the CoffeeScript documentation. Additionally, youâll need to have Node.js and the Node Package Manager (npm) installed for this tutorial. If you donât have âem installed, no worries: head over to nodejs.org and download the installer for your platform; then, well, install it! --- Meeting Mocha and Chai Weâll be building the beginnings of a todo list application (clichĂ©, I know). These will be CoffeeScript classes. Then, weâll write some tests with Mocha and Chai to test that functionality. Why both Mocha and Chai? Well, Mocha is a testing framework, but it doesnât include the actual assertions component. That might sound strange: after all, there isnât much more to a testing library, is there? Well, there is, in Mochaâs case. The features that brought me to the library are two-fold: the ability to run tests from the command line (instead of having an HTML page to run them in the browser), and the ability to run test in CoffeeScripts, without having to convert that code to JavaScript (as least manually: Mocha does it behind the scenes). There are other features, too, that I wonât be talking about here, including: * You can easily test asynchronous code. * You can watch for especially slow tests. * You can output the results in a number of different formats. And on, and on. See more at the Mocha home page. To install Mocha simply run npm install -g mocha, and youâre set. As for Chai: itâs a great assertion library that offers interfaces for doing both BDD and TDD; you can use it both in the browser or on the command line via node, which is how weâll use it today. Install it for Node, via npm install -g chai. Now that we have our libraries installed, letâs start writing some code. --- Setting Up Our Project Letâs begin by setting up a mini project. Create a project folder. Then, create two more folders in that one: src, and test. Our CoffeeScript code will go in the src folder, and our tests will go in, you guessed it, the tests folder. Mocha looks for a test folder by default, so by doing this, weâll save ourselves some typing later. Mocha looks for a test folder by default. Weâre going to create two CoffeeScript classes: Task, which will be a todo item, and TaskList, which will be a list of todo items (yes, itâs more than an array). Weâll put them both in the src/task.coffee file. Then, the tests for this will be in test/taskTest.coffee. Of course, we could split âem into their own files, but weâre just not going to do that today. We have to start by importing the Chai library and enabling the BDD syntax. Hereâs how: chai = require 'chai' chai.should() By calling the chai.should method, weâre actually adding a should property to Object.prototype. This allows us to write tests that read like this: task.name.should.equal "some string" If you prefer the TDD syntax, you can do this: expect = chai.expect ⊠which allows you to write tests like this: expect(task.name).to.equal "some string" Weâll actually have to use both of these, as youâll see; however, weâll use the BDD syntax as much as possible. Now weâll need to import our Task and TaskList classes: {TaskList, List} = require '../src/task' If you arenât familiar with this syntax, thatâs CoffeeScriptâs destructured assignment at work, as well as some of its object literal sugar. Basically, our require call returns an object with two properties, which are our classes. This line pulls them out of that object and gives us two variables named Task and TaskList, each of which points to the respective class. --- Writing Our First Tests Great! Now, how about a test? The beauty of the Mocha syntax is that its blocks (describe and it) are identical to Jasmineâs (both being very similar to RSpec). Hereâs our first test: describe 'Task instance', -> task1 = task2 = null it 'should have a name', -> task1 = new Task 'feed the cat' task1.name.should.equal 'feed the cat' We start with a describe call: all these tests are for a Test instance. By setting test1 = test2 = null outside our individual tests, we can use those values for multiple tests. Then, in our first test, weâre simply creating a task and checking to see that its name property has the correct value. Before writing the code for this, letâs add two more tests: it 'should be initially incomplete', -> task1.status.should.equal 'incomplete' it 'should be able to be completed', -> task1.complete().should.be.true task1.status.should.equal 'complete' Ok, letâs run these tests to make sure theyâre failing. To do this, letâs open a command prompt and cd to your project folder. Then, run this command: mocha --compilers coffee:coffee-script Mocha doesnât check for CoffeeScript by default, so we have to use the --compilers flag to tell Mocha what compiler to use if it finds a file with the coffee file extension. You should get errors that look like this: If, instead of seeing that, you get the error Cannot find module '../src/task', itâs because your src/task.coffee file doesnât exist yet. Make said file, and you should get said error. --- Coding Our First Features Well, now that we have failing tests, itâs time to write the code, correct? Open that src/task.coffee file and letâs get cracking. class Task constructor: (@name) -> Just this is enough to get our first test passing. If you arenât familiar with that parameter syntax, that just sets whatever value was passed to new Task to the @name (or this.name) property. However, letâs add another line to that constructor: @status = 'incomplete' Thatâs good. Now, head back to the terminal and re-run our tests. Youâll find thatâwait a second, nothingâs changed! Why arenât our first two tests passing? A simple problem, actually. Because the CoffeeScript compiler wraps the code in each file in a IIFE (or, a self-invoking anonymous function), we need to âexportâ anything we want to be accessible from other files. In the browser, youâd do something like window.Whatever = Whatever. For Node, you can use either global or exports. Weâll be using exports, since 1) thatâs considered best practice, and 2) thatâs what we prepared for when setting up our tests (remember our require call?). Therefore, at the end of our task.coffee file, add this: root = exports ? window root.Task = Task With that in place, you should find that two of our three tests are now passing: To get the last test to pass, weâll have to add a complete method. Try this: complete: -> @status = 'complete' true Now, all tests pass: Nowâs a good time to mention that Mocha has a number of different reports: these are just different ways to output the test results. You can run mocha --reporters to see your options: By default, Mocha uses the dot reporter. However, I prefer the spec reporter, so I tack -R spec on the end of command (-R is the reporter-setting flag). --- Adding a Feature Letâs add a feature to our Task class: weâll let tasks be dependent on other tasks. If the âparentâ task isnât completed, the âchildâ task canât be done. Weâll keep this feature simple and allow tasks to have only one sub-task. We also wonât check for recursiveness, so while it will be possible to set two tasks to be the parent and child of each other, it will render both tasks incomplete-able. Tests first! it 'should be able to be dependent on another task', -> task1 = new Task 'wash dishes' task2 = new Task 'dry dishes' task2.dependsOn task1 task2.status.should.equal 'dependent' task2.parent.should.equal task1 task1.child.should.equal task2 it 'should refuse completion it is dependent on an uncompleted task', -> (-> task2.complete()).should.throw "Dependent task 'wash dishes' is not completed." Task instances are going to have a dependsOn method, which tasks the task that will become their parent. Tasks that have a parent task should have a status of âdependent.â Also, both tasks get either a parent or child property that points to the appropriate task instance. In the second test there, we say that a task with an incomplete parent task should throw an error when its complete method is called. Notice how test syntax works: we need to call should off of a function, and not the result of the function: therefore, we wrap the function in parentheses. This way, the test library can call the function itself and check for the error. Run those tests and youâll see that both fail. Coding time! dependsOn: (@parent) -> @parent.child = @ @status = 'dependent' Again, very simple: we just set the task parameter to the parent task, and give it a child property which points to this task instance. Then, we set the status of this task to be âdependent.â If you run this now, youâll see that one of our tests is passing, but the second isnât: thatâs because our complete method doesnât check for an uncompleted parent task. Letâs change that. complete: -> if @parent? and @parent.status isnt 'completed' throw "Dependent task '#{@parent.name}' is not completed." @status = 'complete' true Hereâs the completed complete method: if thereâs a parent task, and it isnât completed, we throw an error. Otherwise, we complete the task. Now, all tests should pass. --- Building the TaskList Next, weâll build the TaskList class. Again, weâll start with a test: describe 'TaskList', -> taskList = null it 'should start with no tasks', -> taskList = new TaskList taskList.tasks.length.should.equal 0 taskList.length.should.equal 0 This is old-hat to you by now: weâre creating a TaskList object and checking its tasks and length properties to makes sure their both at zero. As you might guess, tasks is an array that holds the tasks, while length is just a handy property that weâll update when adding or removing tasks; it just saves us from having to write list.tasks.length. To make this test pass, weâll make this constructor: class TaskList constructor: () -> @tasks = [] @length = 0 Good start, and that gets our test passing. Weâll want to be able to add tasks to a task list, right? Weâll have an add method that can take either a Task instance, or a string which it will convert to a Task instance. Our tests: it 'should accept new tasks as tasks', -> task = new Task 'buy milk' taskList.add task taskList.tasks[0].name.should.equal 'buy milk' taskList.length.should.equal 1 it 'should accept new tasks as string', -> taskList.add 'take out garbage' taskList.tasks[1].name.should.equal 'take out garbage' taskList.length.should.equal 2 First, we add an actual Task object, and check the taskList.tasks array to verify that itâs been added. Then, we add a string, and make sure that a Task object with the right name was added to the tasks array. In both cases, we check the length of taskList as well, to make sure that itâs being property updated. And the function: add: (task) -> if typeof task is 'string' @tasks.push new Task task else @tasks.push task @length = @tasks.length Pretty self-explanatory, I think. And now our tests pass: Of course, we might want to remove tasks from our list, right? it 'should remove tasks', -> i = taskList.length - 1 taskList.remove taskList.tasks[i] expect(taskList.tasks[i]).to.not.be.ok First, we call the remove method (yet to be written, of course), passing it the last task currently in the list. Sure, we could just hardcode the index 1, but Iâve done it this way because that makes this test flexible: if we changed our previous tests or added more tests above this one, that might have to change. Of course, we have to remove the last one because otherwise, the task after it will take its place and thereâll be something at that index when weâre expecting there to be nothing. And speaking of expecting, notice that weâre using the expect function and syntax here instead of our usual should. This is because taskList.tasks[i] will be undefined, which doesnât inherit from Object.prototype, and therefore we canât use should. Oh, yeah, we still need to write that remove function: remove: (task) -> i = @tasks.indexOf task @tasks = @tasks[0...i].concat @tasks[i+1..] if i > -1 @length = @tasks.length Some fancy array footwork combined with CoffeeScriptâs ranges and array splicing shorthand closes this deal for us. Weâre simply splitting off all the items before the one to remove and all the items after it; the we concat those two arrays together. Of course, weâll update @length accordingly. Can you say âpassing testsâ? Letâs do one more thing. We want to print our a (relatively) nice-looking list of the current tasks. This will be our most complex (or at least, our longest) test yet: it 'should print out the list', -> taskList = new TaskList task0 = new Task 'buy milk' task1 = new Task 'go to store' task2 = new Task 'another task' task3 = new Task 'sub-task' task4 = new Task 'sub-sub-task' taskList.add task0 taskList.add task1 taskList.add task2 taskList.add task3 taskList.add task4 task0.dependsOn task1 task4.dependsOn task3 task3.dependsOn task2 task1.complete() desiredOutput = """Tasks - buy milk (depends on 'go to store') - go to store (completed) - another task - sub-task (depends on 'another task') - sub-sub-task (depends on 'sub-task') """ taskList.print().should.equal desiredOutput Whatâs going on here? First, weâre creating a new TaskList object so that we start from scratch. Then, we create five tasks and add them to taskList. Next, we set up a few dependencies. Finally we complete one of our tasks. Weâre using CoffeeScriptâs heredoc syntax to create a multi-line string. As you can see, weâre keeping it pretty simple. If a task has a parent task, itâs mentioned in parentheses after the task name. If a task is completed, we put that, too. Ready to write the function? print: -> str = "Tasks\n\n" for task in @tasks str += "- #{task.name}" str += " (depends on '#{task.parent.name}')" if task.parent? str += ' (complete)' if task.status is 'complete' str += "\n" str Itâs actually pretty straightforward: we just look over the @tasks array and add âem to a string. If they have a parent, we add that, and if theyâre complete, we add that too. Notice that weâre using the modifier form of the if statement, to tighten up our code. Then, we return the string. Now, all our tests should pass: --- Wrapping Up Try adding a few features to get the hang of it all. Thatâs the extent of our little project today. You can download the code from the top of this page; in fact, why donât you try adding a few features to get the hang of it all? Here are a few ideas: * Prevent Task instances from being able to depend on each other (recursive dependencies). * Make the TaskList::add method throw an error if it receives something other than a string or a Task object. These days, Iâm finding CoffeeScript more and more attractive, but the biggest downside to it is that it must be compiled to JavaScript before itâs useful. Iâm grateful for anything that negates some of that workflow breaker, and Mocha definitely does that. Of course, itâs not perfect (since itâs compiling to JS before running the code, line numbers in errors donât match up with your CoffeeScript line numbers), but itâs a step in the right direction for me! How about you? If youâre using CoffeeScript, how have you been doing testing? Let me know in the comments. http://goo.gl/SlgLg Reme Le Hane