These are my notes based on the phenomenal tutorial by Kent C. Dodds hosted by egghead.io both of which are my go to sources when I want to learn something new. I highly suggest you sign up for the pro subscription.
Part 1 | Part 2 | >Part 3 | Part 4 | Part 5 | Part 6 | Part 7
Part 3: Setting up Unit Testing
There are tons of testing libraries out there. I am going to stick with the more common ones mocha and chai for now. In later parts I will introduce a few more. In order to use them we should install them!
Head to your trusty console:
$ npm i -D mocha chai
the above is the same as ‘npm install mocha chai –save-dev’ only faster!
Create index-test.js in the same directory.
$ touch src/index.test.js
Now looking at our index.js we see we have a few things to test. At least 4 since we do have 4 methods.
module.exports = { list: list, single: single, startsWithLetter: startsWithLetter, numberOfNames: numberOfNames }
Let’s start building our index.test.js. We will first wrap our initial section in a describe block. This is just a container to describe what we should expect to see tested inside.
var expect = require('chai').expect; var rng = require('./index'); describe('Our first tests', function () { });
Our list method should return an array containing strings. So let’s test for that!
describe('tests should be running', function () { describe('list()', function () { it('Expect it to return an array of strings', function () { expect(rng.list()).to.satisfy(isArrayOfStrings); function isArrayOfStrings(array) { return array.every(function(item){ return typeof item === 'string'; }) } }); }); });
Let’s take a closer look at each part in the above test. We have another describe block, this time focused on the all method. This is a nice way of grouping our tests to keep them focused. The first argument in describe is a string denoting the topic, the second is a function that will contain our actual tests and also could be used to set up mock data if we needed it.
The it is similar to describe in that you first give it a string but this time it will be much more specific. This is what you are actually testing. In this example we want to make sure that the all function returns an array of strings. The second argument is again a function block where we will do the specific test.
One of the benefits of this style of testing is to create very readable tests. Even if you don’t know the nitty gritty you could read the test and it will explain itself. Using expect or should gives you a clear idea of what we are looking for. They are pretty similar, however there are some syntactical differences to be aware of. (read more)
These allow you to chain together natural language assertions. Expect takes the value you want to test and then you chain together what assertions you need to use in order to test. The language used to chain the expect and the test generally do not provide testing functionality.
List of Chains: .to .be .been .is .that .which .and .has .have .with .at .of .same
These get chained together finally ending in an actual test assertion. For example in my test we see:
expect(rng.list()).to.satisfy(isArrayOfStrings);
This means that we expect that when we execute rng.list() that it will satisfy the given truth test. Here our truth test is another function that checks to see if every item in the array has a type of string. This is very flexible and will help you easily generate tests. The full api can be read here. Here are some of the ones I use most:
.match(regexp)
Asserts that the target matches a regular expression.
expect('foobar').to.match(/^foo/);
.instanceof(constructor)
Asserts that the target is an instance of constructor
.
var Tea = function (name) { this.name = name; }
, Chai = new Tea('chai');
expect(Chai).to.be.an.instanceof(Tea);
expect([ 1, 2, 3 ]).to.be.instanceof(Array);
.equal(value)
Asserts that the target is strictly equal (===
) to value
. Alternately, if the deep
flag is set, asserts that the target is deeply equal to value
.
expect('hello').to.equal('hello');
expect(42).to.equal(42);
expect(1).to.not.equal(true);
expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' });
expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' });
var expect = require('chai').expect; var randomNameGenerator = require('./index'); describe('tests should be running', function () { describe('list()', function () { it('should be an array of strings', function () { expect(randomNameGenerator.list()).to.satisfy(isArrayOfStrings); function isArrayOfStrings(array) { return array.every(function(item){ return typeof item === 'string'; }) } }); it('should contain `FirstName MiddleName LastName`', function () { expect(randomNameGenerator.list()).to.include('FirstName MiddleName LastName'); }); }); describe('single()', function () { it('should be a string', function () { expect(randomNameGenerator.single()).to.be.a('string'); }); it('should contain three names', function () { var arrayOfName = randomNameGenerator.single().split(' '); expect(arrayOfName).to.have.lengthOf(3); }); }); describe('startsWithLetter()', function () { it('should be a string', function () { expect(randomNameGenerator.startsWithLetter('A', 'C', 'E')).to.be.a('string'); }); it('should contain three names', function () { var arrayOfName = randomNameGenerator.startsWithLetter('A', 'C', 'E').split(' '); expect(arrayOfName).to.have.lengthOf(3); }); it('should start with the passed values for f,m,l of A, C, E', function () { var arrayOfName = randomNameGenerator.startsWithLetter('A', 'C', 'E').split(' '); expect(arrayOfName[0][0]).to.equal('A'); expect(arrayOfName[1][0]).to.equal('C'); expect(arrayOfName[2][0]).to.equal('E'); }) }); describe('numberOfNames()', function () { it('numberOfNames(3) should have a length of three', function () { var arrayOfNames = randomNameGenerator.numberOfNames(3); expect(arrayOfNames).to.have.lengthOf(3); }); it('numberOfNames() should default to one', function () { var arrayOfOneName = randomNameGenerator.numberOfNames(); expect(arrayOfOneName).to.have.lengthOf(1); }); }); });
Now that we have some basic tests we will create a script to run them. Open package.json and in the scripts section we will change the test to:
"test": "mocha src/index.test.js -w"
The -w watches the file system for changes and re-runs the test. There are many more options head to mochajs.org and search for usage:
There we go, we have set up a basic testing framework!
Next… Automating releases
Key:
= Time Saving Idea
= Pro Tip
= Note
= Alert
= Code Update
= A Closer Look