Writing test for JavaScript with
Testing, 1.. 2.. 3..
Mocha and Chai
Test Driven Development
The idea that you write tests first, then you write code.
As a result, you think about how you will use the code before you implement it.
assert(isEven(2), true)
function isEven(n) {
return n % 2 === 0;
}
FAIL
PASS
Red, Green, Refactor
Write just enough code
Unit tests should be specific, and so should the code to make the test pass.
Once it passes, refactor, and move on to the next test.
Common pitfalls
- Forgetting to run tests frequently
- Writing too many tests at once
- Writing tests that are too large or coarse-grained
- Writing overly trivial tests, for instance omitting assertions
- Writing tests for trivial code, for instance accessors
Mocha is a feature-rich JavaScript testing framework that runs on both Node and in the browser.
describe('add()', function () {
it('should add two numbers together', function () {
expect(add(1, 2)).to.equal(3);
});
it('should not concatenate a string and a number together', function() {
expect(add("1", 2)).to.not.equal("12");
});
});
Mocha
$ mocha
function add(x, y) {
return x + y;
}
Why doesn't this pass all the tests?!
describe('Suite', function () {
it('should do something', function () {
// Test code goes here.
});
it('should do more things', function() {
// More test code goes here.
});
});
There are two important functions when writing tests:
-
describe() – Groups test cases into test suites.
- it() – Individual test cases.
Hooks
Hooks allow you to set up both the state before and after your tests run.
describe('hooks', function() {
before(function() {
// runs before all tests in this block
});
after(function(){
// runs after all tests in this block
});
beforeEach(function(){
// runs before each test in this block
});
afterEach(function(){
// runs after each test in this block
});
// test cases
})
describe('fillArray', function () {
var array;
beforeEach(function () {
array = []; // Reset the array before each test runs.
});
it('should fill an array with a value, n-times', function () {
fillArray(array, "Hello", 3);
expect(array).to.deep.equal(["Hello", "Hello", "Hello"]);
});
it('should only fill a positive amount of times', function() {
fillArray(array, "Hi", -1);
expect(array).to.be.empty;
});
});
Let's take a look at the beforeEach() hook, which resets the array before each test case is ran.
Skipping tests
Sometimes it's useful to skip tests when you don't know how to implement a test, but know it needs to be done.
describe('shuffleArray', function() {
it.skip('should be sorted in random order');
});
Text
File watcher support
Automatically runs tests whenever a file is changed.
$ mocha --watch
An assertion library for node and the browser that can be paired with any JavaScript testing framework.
Chai
describe('add()', function() {
it('should add two numbers together', function(){
var sum = add(4, 2);
expect(sum).to.equal(6);
});
});
Chai!
Should interface
Extends each object with a should property to start your assertion chain.
var foo = 'bar',
beverages = {tea: [ 'chai', 'matcha', 'oolong']};
foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.length(3);
beverages.should.have.property('tea').with.length(3);
Expect interface
Chai provides an expect() function which lets you start your assertion chain.
var foo = 'bar',
beverages = {tea: [ 'chai', 'matcha', 'oolong']};
expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.length(3);
expect(beverages).to.have.property('tea').with.length(3);
Unlike the should property, the expect function works on values that may be undefined.
Expect interface
Sometimes assertions can be difficult to understand unless you look at the actual test.
describe('turnOn()', function () {
var lightbulb;
before(function () {
lightbulb = { isOn: false };
});
it('should turn on a lightbulb', function () {
turnOn(lightbulb);
expect(lightbulb.isOn).to.be.true;
});
});
What is this false value?
Expect interface
expect() allows us to pass in a string to help tests easier to understand at a glance.
describe('turnOn()', function () {
var lightbulb;
before(function () {
lightbulb = { isOn: false };
});
it('should turn on a lightbulb', function () {
turnOn(lightbulb);
expect(lightbulb.isOn, 'lightbulb.isOn should be true').to.be.true;
});
});
Language Chains
You may have noticed that these tests read a lot like English.
There are some words Chai provides to "chain" assertions together so that they are easy to read.
- to
- be
- been
- is
- that
- which
- and
- has
- have
- with
- at
- of
- same
Language Chains
These mean the exact same thing:
expect(1).to.equal(1);
expect(1).equal(1);
expect(1).to.be.that.which.is.to.equal(1);
Equality with Objects and Arrays
Checking for equality between objects and arrays can be tricky in JavaScript.
Equality with Objects and Arrays
Luckily, Chai provides us with deep.equal (or eql), which compares each value inside objects and arrays for us.
var myObject = { name: "Mocha" };
expect(myObject).to.deep.equal({ name: "Mocha" });
var myArray = [1, 2, 3];
expect(myArray).to.eql([1, 2, 3]);
More ways to compare!
There are many more ways to compare values than equality, please read the docs!
- a
- above
- all
- an
- any
- arguments
- below
- change
- changes
- closeTo
- contain
- contains
- decrease
- decreases
- deep
- deep.equal
- deep.property
- empty
- eq
- eql
- eqls
- equal
- equals
- exist
- false
- greaterThan
- gt
- gte
- haveOwnProperty
- include
- includes
- increase
- increases
- instanceof
- itself
- key
- keys
- least
- length
- lengthOf
- lessThan
- lt
- lte
- match
- members
- most
- not
- null
- ok
- ownProperty
...plus more!
(But seriously, write them sooner)
Testing, 1.. 2.. 3...
By Tony Gaskell
Testing, 1.. 2.. 3...
Writing tests for JavaScript using Mocha and Chai
- 2,065