Testing Is Caring
Mocha.js
Testing provides checkpoints for your project to reach, exceed and look back on. While it’s tempting to think that it’s there only to catch bugs, that’s actually not really the case. The tests are written by humans who are just as fallible as those who wrote the app, after all. Instead, testing is there to specify the functionality of your app during development; to communicate to future developers on what not to break; and to prevent bugs for as much as you can foresee them occurring based on functionality. You’re not looking for what’s broken, you’re looking for what to make work.
Luckily there’s a lot of options out there for JavaScript testing. Let’s start with unit testing using Mocha.js as it represents the archetypical JavaScript testing framework. Simply install it into your project (mocha’s installation guide gives you plenty of options depending on your needs), and create a test folder where any newcomer to the project can easily find it.
You’ll also need some assertion tools. These tools are whats really doing the testing. Mocha will be the framework which run the assertions. Under the hood, an assertion’s only job is to throw an error, then it’s the frameworks job to catch it for reporting. Assertion determines whether your statement about some executed code is true. You “assert” that “2 + 2” equals “4”. And when it doesn’t, well now you know what to work on. Your tests will just be a series of these assertions. Chai is a great place to start for all your assertion needs. It’s a popular separate library that plays perfectly well with Mocha. Go ahead and install Chai along with Mocha and require Chai up top, like so:
var chai = require('chai')
// alternatively, with ES6
import chai from 'chai'
Mocha will be installed and available globally in your development environment. From Chai, extract your assertion functions:
var assert = chai.assert;
var should = chai.should();
var expect = chai.expect;
Each one of these assertion types do very similar things with slightly different syntax. `assert` is your basic assertion function for unit-testing; and `should` and `expect` are designed for BDD, “Behavior Driven Development.” Unit-testing means performing assertions on a more granular level, as in specific functions and the data they handle. Very minute stuff. BDD describes testing based on events, like user inputs, so you’ll have many interlinking function calls occurring during these types of assertions. Its like the next level up from unit-testing.
Let’s begin at the beginning and define tests. These tests should be characterizing your API. If TDD is all about specifying your application that means we’ll be writing the tests ahead of the actual code. Does this sound backwards? It is. If we were only looking for what might be broken, then what would QA be? TDD is different. TDD looks forward. Known to the rest of the world as planning. Let’s plan a todo app.
Our app should simply add some todos to a list. Where do we start? One function at a time. Perfect for unit-testing. Well, what’s the list? This is all just a simple demonstration, so for now it’s just an array. But, what’s a “todo”? Let’s just say it’s a string. Before we continue, notice how we’re asking ourselves and then answering these questions. We’re already experiencing the benefits of TDD. Here’s some code to layout the exchange in testing syntax.
describe('#addToList', function(){
it('addToList function should exist', function(){
// you gotta previously expose your elements before testing
assert.isFunction(addToList, 'we're gonna need this')
}
...
}
We asked the first question with this test, now it’s your job to provide the correct answer. `assert` will give us a pass or fail, and the text beside it will print when it passes. Each function body will be visualized as different sections in our test results (see Mocha’s docs for the different ways to output your tests).
...
it('should add todos to an array', function(){
addToList('get lunch') // list should now have one element
expect(todos).to.have.length(1) // more exposure of app elements
addToList('get dessert')
expect(todos).to.have.length(2)
}
...
Using different types of assertions means using slightly different syntax. We ran two assertions in the one `it` test. These assertions are built to throw errors. As long as none of them do, `it` will pass as a single test. And the `it`s will all be nested in the bigger section of describe. More visual compartmentalization.
...
it('todos must be strings', function() {
// artificially trigger a jquery event to mimic user behavior
var e = jQuery.Event( "submit" );
e.eventData = "This is a string";
jQuery( "#todo-input" ).trigger( e );
todos.forEach(function(item){
item.should.be.a('string') // yet another type of assertion
}
}
...
This last one leaves a little more to the imagination, but that “little more” can be filled in by your code. It starts with a behavior and should end up as a string in our list. Whatever happens in between should NOT result in anything but a string. If that’s important, we should test for it. The room also implies an intermediary function would be written, thus forcing us to be quite modular with decoupled functions. It may seem we’re playing to the tests a little bit, and we are. Causing our code to be testable means we’re also causing our code to be accessible and modular. Utterly testable code is also utterly maintainable and expandable code. The theme is the same, and it results in API-driven design.
And with that we have the prompts, mental sketch and makings of our soon-to-be application! TDD demonstrates that these prompts can, and in many ways, should be the beginning of our app. And then for future development, there’s a record of where we’ve been and which direction we’re headed. The tests serve as a list of functionalities the original designer valued the most.
If you still think that this is hardly enough to define an app; that this doesn’t give enough insight and there could be better prompts for quality code; or that this particular example isn’t demonstrative enough to make a sturdy, robust app. Then, I would agree. It just means you should write more tests for it.