Testing with Mocha/Chai/Sinon: Quick Start Guide

If you aren’t in the habit already, writing tests for your code is an excellent way to stay disciplined as a software engineer on any project. If you don’t think that it’s worth your time, but wish you had a way to thoroughly check your code as you go along, this quick start guide will be perfect for you. I’ll cover the basics of unit testing, integration testing, and end-to-end testing using the easiest possible tech stack to get you up and testing in just a few minutes.


Setup:

Before starting, go ahead and install the Mocha test framework as a development dependency for your project. Mocha is a widely used JavaScript test framework running on Node.js, which makes testing really easy. Click here to check out their (excellent) docs.

$ npm install mocha --save-dev

Next, you’ll need the Chai assertion library, which will make your coding feel like writing regular english. Run the command below to install via npm.

$ npm install chai --save-dev

Lastly, install the Sinon test framework, which will allow for the use of spies, stubs, and mocks. More on that later.

$ npm install sinon --save-dev

Unit Testing

Now, we’re ready to open a new file and start writing the tests. We’ll start with the easy kind, unit tests. These are small tests that allow you to check the functionality of one small aspect of your program which does not interact or depend much on other elements. As an example, I’ll write some tests for a method of an object in my code.

var assert = chai.assert;
var should = chai.should();
describe('Tests for todo.app', function() {
  describe('the trimTodoName function', function() {
    it('should remove leading whitespace', function() {
todo.util.trimTodoName(' name').should.equal('name');
});
    it('should remove trailing whitespace', function() {
todo.util.trimTodoName('name ').should.have.length(4);
});
    it('should remove leading and trailing whitespace', function() {
assert(todo.util.trimTodoName(' name '), 'name');
});
  });
}

In the example above, I set the stage for test writing using mocha’s describe function. The first argument is a string the explains what you are testing, and the second argument is a function where you will put your actual test. You’ll notice that I have nested describe statements to be more specific about what each block is actually testing.

Next, I write out the actual tests using Mocha’s ‘it’ construction. Again, the first argument is a string that says what you are testing, and the second argument is a function that will run a test. Here is where Chai’s assertion library comes into play. Check out the code below, which is taken out of the example above:

todo.util.trimTodoName('   name').should.equal('name');

Within the function of the ‘it’ block, this line basically says ‘todo.util.trimToDoName(‘ name’) should equal ‘name’. I told you it would look like english! That’s thanks to Chai’s ability to string together chainable assertions. Keep in mind that even though they look like english sentences, they are just regular old functions. The line above uses the ‘should’ style of Chai testing, but you can make the same test using Chai’s ‘assert’ or ‘expect’ constructions. In fact, I use the ‘assert’ style in the third test of the example code above.

Now you have officially made the first tests for your code! Depending on how you set up your file structure, or if you are using a build system / test runner, you should be able to run npm test and see a readout with your test results.


Integration Testing

Now we’ll get into some more extensive tests. Integration tests are used when you have code that depends on other modules and therefore can’t be isolated. Let’s say we had a server that we wanted to make sure was sending a specific bit of data when a certain function was called. I’ll explain, in steps, how we will pull this off using a few handy functions from the Sinon library.

  1. Use the Mocha ‘describe’ function to set the stage for our tests, using it in exactly the same way as our previous example.
  2. Use Mocha’s ‘before’ function to set up all the ‘fake’ aspects of the test. Within the before function, we can write any code that we want to run before the ‘it’ function, which is where the real test logic goes.
  3. Within the ‘before’ block, we’ll make a JSON object that our fake server will send as a response when it gets pinged.
  4. Here, we will make a stub and a fake server using two Sinon library functions. A stub is a function (a ‘spy’) that we can give some pre-programmed behavior. In this case, we will ‘stub out’ the ‘setup’ function, which will allow us to see how many times it got called during the test. On the line below, we use the sinon.fakeServer.create() function and assign it to a variable. Now, we can give that fake server a pre-set response and make sure our test stub responds properly.
  5. Now that we have our fake server, we can give it a pre-programmed response to send after it is called. If our ‘setup’ function works properly, it will be called and send back the JSON object after our fake server sends it’s GET request.
  6. Finally, now that all our setup is done, we can write out the actual test. We can do this in exactly the same way as we did in our unit tests. First we will call todo.init() and server.respond(). At this point, the setup stub should have responded with the pre-determined JSON object, so we will write two ‘assert’ functions to verify this. Full code is below.
//step1
describe('API integration', function(){
//step2
before(function(){
//step3
var JSONresponse = JSON.stringify(
{todos: [{name: 'test1',done: true}] }
)
//step4
var setupStub = sinon.stub(todo, 'setup')
var server = sinon.fakeServer.create();
//step5
server.respondWith
(
"GET",
"http://localhost:3000/todos",
[200, {"Content-Type":"application/json"}, JSONresponse]
)
})
//step6
it('todo.setup receives an array of todos after todo.init()',
function() {
todo.init();
server.respond();
assert(setupStub.calledOnce);
assert(setupStub.calledWith(JSON.parse(JSONresponse.todos))
});
});

End to End Testing

Even more thorough than integration tests are full end-to-end tests. Like the name implies, these tests can ensure that at the end of the day, the user will see what they are supposed to when certain input is provided to your web app. They accomplish this by running their own version of a browser, and then executing pre-specified commands before checking whether or not they got the right response back. Once more, install via npm:

npm install casperjs

Now, we can use casper.test.begin() to start writing the test. In this example, I’ll demonstrate how I test that my to-do app is properly showing a list after an item is added. I’ll break it down in steps again:

  1. Call casper.test.begin, passing in a string (the name of your test) as the first argument, the number of asserts you plan to use as the second argument, and lastly is a ‘suite’ function where you will place the logic for the test.
  2. Within the function, call casper.start with a url and an anonymous function. This is where you will put the rest of the logic.
  3. Step 3: Tell casper what it should do before the test is run. Think of it like a ‘ghost user’ that does something specific to your app magically before the test is run. In this case, I want to fill the ‘to-do’ form in my app with an item, which I will later check to make sure it exists. If it does, then I’ll know my app allows users to successfully add items to the todo list. There is a very wide array of things that casper allows you to do to your app besides fill in search fields before the test is run.
  4. Now, we can actually write a test to make sure the item now exists in the to do list.
  5. Anything you want casper to do after running the tests. It almost always simply calls “test.done()”

That’s all there is to it. Now you can run your test and see a nice readout with all the http requests that were executed, the amount of time it took to do so, and of course, whether or not your test passed!