This tutorial is intended to show you the theory of TDD and how you can use this in your node application using chai and mocha.
In this article, I’ll show you how we can make tests using two awesome tools: chai and mocha.
But first of all, I’d like to tell about the importance of testing. Why tests are so necessary? For this, I should explain what TDD is.
The Test-Driven Development (TDD) is a software engineering process that requires unit tests to be written before the code they are supposed to validate that relies on the repetition of a very short development cycle, where the requirements are transformed in test cases. With this, firstly the code will fail (miserably), then the developer should write clean code that works to make the tests pass.
The cycle is very simple. The developer writes a test, this test will fail because you haven’t developed any code for these tests, so the developer should check if the tests are failing. After this, the developer writes clean code to make the tests pass. Again, the developer should check if all tests are passing, if not, it should go back to write code. And, finally, we should remember to make our code always better and refactor it to create new tests.
Having this in mind, we can talk about chai and mocha.
Mocha is a JavaScript test framework running on Node.js and in the browser. Mocha allows asynchronous testing, test coverage reports, and use of any assertion library.
Chai is a BDD / TDD assertion library for NodeJS and the browser that can be delightfully paired with any javascript testing framework.
Basically, mocha is a framework and chai is a library. Let’s go a little deeper in mocha.
Mocha uses hooks to organize its structure. Let’s talk about them.
Let’s see some scenarios
In this case, we can just call a solitary it(), mocha will run this only test.
it('Homer should drink beer', () => {
/** Test cases */
})
mocha-one-test-case.js
In this case, we can nest some describes() hooks and finally call it() to execute the test.
describe('Abraham Simpson', () => {
describe('Homer Simpson', () => {
describe('Bart Simpson', () => {
it('Bart should skate', () => {
/** Test cases */
})
})
})
})
mocha-one-nested-test-case.js
Here, inside the describe() we have two it() that will execute the tests.
describe('Homer Simpson', () => {
it('Bart should skate', () => {
/** Test cases for Bart */
})
it('Lisa should play sax', () => {
/** Test cases for Lisa */
})
})
mocha-two-tests-in-one-test.js
In this scenario, before the first it() mocha will execute what is inside before(), but only once.
describe('Springfield', () => {
before(() => {
console.log('Marge calls Barge and Lisa (you see this only once)')
})
it('Bart should hear his mother', () => {
/** Test cases */
})
it('Lisa should hear her mother', () => {
/** Test cases */
})
})
mocha-run-just-once-before-first-test-case.js
On the contrary of before(), beforeEach() is executed each time for each it() [or describe()] that we have. If we have one it(), it will be executed just once. if we have two it() it will be executed twice. If we have three it() it will be executed three times and so go on.
describe('Springfield', () => {
beforeEach(() => {
console.log('Marge calls Barge and Lisa (you see this twice)')
})
it('Bart should hear his mother', () => {
/** Test cases */
})
it('Lisa should hear her mother', () => {
/** Test cases */
})
})
mocha-run-once-before-each-test-case.js
In this last scenario, mocha will nest the describe() and execute it().
describe('Homer Simpson', () => {
describe('Bart Simpson', () => {
it('Bart should skate', () => {
/** Test cases */
})
})
describe('Lisa Simpson', () => {
it('Lisa should play sax', () => {
/** Test cases */
})
})
})
mocha-two-test-big-test.js
To see it working, let’s do firstly a simple case: A calculator. Let’s create a test case where our calculator should calculate the following operations:
We’ll use all the TDD structure using mocha.
First, let’s create a project called mocha-chai-tests.
$ mkdir mocha-chai-tests
$ cd mocha-chai-tests
$ yarn init
or
$ npm init -y
Install chai and mocha.
$ yarn add chai mocha
or
$ npm i chai mocha --save
Inside the project mocha-chai-tests, create a folder called tests and a file called calc.js.
$ mkdir tests
$ touch tests/calc.js
Now, we can create our first test. To do this, we’ll create a describe() indicating that we’re doing tests on a calculator and another one indicating the addition operation. Let’call a function called add that will sum two numbers. Let’s test if 1 plus 1 will be 2.
const chai = require('chai')
const expect = chai.expect
const calculator = require('../src/calculator')
describe('Calculator', () => {
describe('Addition', () => {
it('1 + 1 should be equals to 2', () => {
expect(calculator.add(1, 1)).to.equal(2)
})
})
})
test-calc.js
Hey Sam, but the file src/calculator.js doesn’t exist. How can we handle this?
It doesn’t exist, but it should. So much that, if we run the tests we’ll get an error. Let’s do this to see what happens. Type the following command on terminal:
$ yarn mocha tests/calc.js
So, to fix this let’s create the folder src and an empty file called src/calculator.js.
$ mkdir src
$ touch src/calculator.js
If you run the tests again you’ll see another result.
$ yarn mocha tests/calc.js
Realize that you can see the structure contained by the Calculator, the Addition, and the failing of our test. The tests tell us the problem is calculator.add is not a function. This is because we do not have any code in the src/calculator.
Before to start developing the functions on src/calculator.js, let’s improve our tests.
const chai = require('chai')
const expect = chai.expect
const calculator = require('../src/calculator')
describe('Calculator', () => {
describe('Addition', () => {
it('1 + 1 should be equals to 2', () => {
expect(calculator.add(1, 1)).to.equal(2)
})
it('should sum two numbers', () => {
expect(calculator.add(2, 2)).to.equal(4)
expect(calculator.add(50, 39)).to.equal(89)
expect(calculator.add(-31, 32)).to.equal(1)
expect(calculator.add(10000, 89999)).to.equal(99999)
})
})
describe('Subtraction', () => {
it('1 - 1 should be equals to 0', () => {
expect(calculator.subtract(1, 1)).to.equal(0)
})
it('should subtract two numbers', () => {
expect(calculator.subtract(6, 2)).to.equal(4)
expect(calculator.subtract(50, 39)).to.equal(11)
expect(calculator.subtract(-31, 32)).to.equal(-63)
expect(calculator.subtract(10000, 89999)).to.equal(-79999)
})
})
describe('Multiplication', () => {
it('1 * 1 should be equals to 1', () => {
expect(calculator.multiply(1, 1)).to.equal(1)
})
it('should multiply two numbers', () => {
expect(calculator.multiply(3, 2)).to.equal(6)
expect(calculator.multiply(50, 39)).to.equal(1950)
expect(calculator.multiply(-31, 32)).to.equal(-992)
expect(calculator.multiply(-5, -2)).to.equal(10)
})
})
describe('Division', () => {
it('1 / 1 should be equals to 1', () => {
expect(calculator.divide(1, 1)).to.equal(1)
})
it('should divide two numbers', () => {
expect(calculator.divide(4, 2)).to.equal(2)
expect(calculator.divide(50, 5)).to.equal(10)
expect(calculator.divide(-15, 2)).to.equal(-7.5)
})
it('should return NaN if the denominator is zero', () => {
expect(calculator.divide(4, 0)).to.equal(undefined)
expect(calculator.divide(50, 0)).to.equal(undefined)
expect(calculator.divide(-15, 0)).to.equal(undefined)
})
})
})
tests-calc.js
Explaining what I did. I create the main structure called Calculator. I’m testing the four math operations: addition, subtraction, multiplication, and division, where I structured them in each describe(). So, in addition, I’m testing if 1+1=2, and them I’m using the random numbers, where I know the result of the sum of them, and making sure if my function is returning the correct value. I’m following the same idea on the subtraction, multiplication, and division. However, in the division, I’m adding a new test: if the denominator (divisor) is zero, we should return undefined, because this operation is not allowed.
For curiosity, you can submit it to tests again and see what happens.
Now we can make the necessary changes to make our tests pass. Let’s edit the file src/calculator.js.
const add = (a, b) => a + b
const subtract = (a, b) => a - b
const multiply = (a, b) => a * b
const divide = (a, b) => b !== 0 ? (a / b) : undefined
module.exports = {
add,
subtract,
multiply,
divide,
}
src-calculator.js
If we submit the tests again…
Now all the tests are passing.
As we know to make tests using chai and mocha, let’s test this using APIs. Let’s create our folder and files. In the same folder mocha-chai-tests, create:
$ touch tests/api.js
And now, let’s install a new dependency: chai-http. It will help us to make requests from a server. And for this, let’s take and advantage and install express.
$ yarn add chai-http express
or
$ npm i chai-http express
For this case, I’ll create a function called createFakeServer, that means it will create a server during the tests and, in the final, will close it. Besides, I’ll use different test cases on chai. I’ll just create an endpoint that will return a particular object with some information and we will validate it.
const chai = require('chai')
const chaiHttp = require('chai-http')
const express = require('express')
const expect = chai.expect
chai.use(chaiHttp)
const createFakeServer = () => {
const app = express()
const apiPort = 30001
const result = {
message: 'Hello World',
value: 3.5,
odds: [1,3,5,7,9],
person: {
id: 123,
name: 'Sam Barros',
}
}
app.get('/', (req, res) => {
res.send(result)
})
app.listen(apiPort)
return app
}
describe('API', () => {
describe('GET /', () => {
let fakeServer
beforeEach(() => {
fakeServer = createFakeServer()
})
it('should return the object', (done) => {
chai.request(fakeServer)
.get("/")
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(200)
expect(res.body).to.be.a('object')
expect(res.body).to.have.property('message')
expect(res.body).to.have.property('value')
expect(res.body.value).to.be.a('number')
expect(res.body).to.have.property('odds')
expect(res.body.odds).to.be.a('array').with.lengthOf(5)
expect(res.body.odds).to.be.an('array').that.does.not.include(2)
expect(res.body.odds).to.deep.include(5)
expect(res.body.odds).to.have.ordered.members([1, 3, 5, 7, 9]).but.not.have.ordered.members([3, 1])
expect(res.body).to.have.property('person')
expect(res.body.person).to.have.property('id')
expect(res.body.person).to.have.property('name')
expect(res.body.person.name).to.be.string
expect(res.body.person.name).to.deep.equal('Sam Barros')
done()
})
})
})
})
mocha-api.js
To close the server at the end of the submission, we should use the “–exit” mocha option.
$ yarn mocha --exit tests/api.js
We should pay attention to something. Because the end function is passed a callback, assertions are run asynchronously. That means a mechanism must be used to notify the testing framework that the callback has completed. Otherwise, the test will pass before the assertions are checked. In the Mocha, this is accomplished using the done callback, which signals that the callback has completed, and the assertions can be verified. 🙂
This tutorial is intended to show you the theory of TDD and how you can use this in your node application using chai and mocha.
I’d like to say thanks to you, my friends, for dedicating time reading this article.
#nodejs #chai #mocha