The Problem Solving Process Using Mocha Testing
This weekend I delved into unit testing using the Mocha JavaScript testing framework. This blog post serves as an introduction to implementing Mocha and using Mocha to solve a problem. The problem I will be highlighting is determining the number of days between any two dates.
Installing and Implementing Mocha
In the project directory, create the package.json file that manages packages for the project using the following command:
npm init
Then, install the mocha package and its associated dependencies.
npm install mocha -D
You can then add the following script to the package.json file. In the scripts object, set “test” to “mocha” as follows:
"scripts" : {
"test" : "mocha"
}
Now, mocha can be executed using,
npm test
Setting Up Unit Testing in Mocha
The implementation code is written in index.js and the testing code is written in index.spec.js in the test directory. Tests should be written to be informative and expressive. Mocha provides a describe and an it function. These both are given a descriptive string and a callback as follows:
describe("description", () => { it("description", () => { }) it("description", () => { })
})
describe is used to block together multiple related tests and it is used to specify individual test cases. Each test should be separated into a setup, exercise, and validation. Setup is where objects, variables and conditions are set up that the test depends on, execute is where the functionality you are testing is executed, and verify is where you check your expectation against the result of the execution.
The verification in mocha is done using functions in the assert library. In index.spec.js, we need to include the following line to import the assert functions:
const assert = require('assert');
The assert functions compare values and throw errors as needed. There are several useful assert functions, but the one showcased here will be assert.equal().
Problem Solving Approach Using Mocha
So, how do we determine the number of days between two dates? Let’s write some test cases in Mocha for the daysBetweenDates function (the functions tested are imported from index.js):
const assert = require('assert')
const { daysBetweenDates } = require('../index')describe("days between examples", () => {
it("returns the correct number of days between 12/10/2019 and 3/15/2020", () => {
const date1 = [2019, 12, 10]
const date2 = [2020, 3, 15]
const output = 96
const result = daysBetweenDates(date1, date2)
assert.equal(result, output)
}) it("returns the correct number of days between 12/10/2019 and 3/15/2021", () => {
const date1 = [2019, 12, 10]
const date2 = [2020, 3, 15]
const output = 461
const result = daysBetweenDates(date1, date2)
assert.equal(result, output)
})})
These two test cases represent the end result we want to achieve, a function that calculates the days between two dates. We program into the tests the hand calculated days in between the dates for the two test cases. These tests will be used to determine if the final implementation is correct.
Whats the best approach to determining the number of days? We can step forward from the initial date and count the number of days until we reach the second date. The pseudocode is as follows:
while date1 is before date2
update date1 to the next date
add one day to the total days counted
There are many helper functions that need to be tested before incorporating them together in the daysBetweenDates function. Let’s first create a function to test if date 1 is before date 2. We will write the tests first. To turn off our tests for days between, .skip can be added after describe as shown below. The tests for isDateBefore are written below:
const assert = require('assert')
const { daysBetweenDates } = require('../index')describe.skip("days between examples", () => {
...
})describe("is date before examples", () => {
it("returns true for 12/10/2019 and 3/15/2020", () => {
const date1 = [2019, 12, 10]
const date2 = [2020, 3, 15]
const result = isDateBefore(date1, date2)
assert.equal(result, true)
} it("returns true for 12/10/2019 and 12/20/2019", () => {
const date1 = [2019, 12, 10]
const date2 = [2019, 12, 20]
const result = isDateBefore(date1, date2)
assert.equal(result, true)
}
})
The isDateBefore function is implement as follows. The function is marked as an export at the top of index.js to allow index.spec.js to import the function:
module.exports = {
daysBetweenDates,
}function isDateBefore(date1, date2) {
const year1 = date1[0]
const month1 = date1[1]
const day1 = date1[2] const year2 = date2[0]
const month2 = date2[1]
const day2 = date2[2] if (year1 < year2) {
return true
} else if(year1 === year2) {
if (month1 < month2) {
return true
} else {
return day1 < day2
}
} else {
return false
}
}
The next function we need to implement is a function that updates date 1 to the next day. In order to do this function, we need a function that determines the number of days in the month and a function that determines if the year is a leap year. The test for isLeapYear is written as follows:
describe("is leap year examples", () => {
it("returns true for 2020", () => {
const year = 2020
const result = isLeapYear(year)
assert.equal(result, true)
})
})
Using the definition of a leap year, we can write the isLeapYear function as follows:
function isLeapYear(year) {
if(year % 400 === 0) {
return true
} else if(year % 100 === 0) {
return false
} else if(year % 4 === 0) {
return true
} else {
return false
}
}
Using isLeapYear, we can write a test for a daysInMonth function as follows:
describe("days in month examples", () => {
it("returns the correct number of days for February 2020", () => {
const month = 2
const year = 2020
const output = 29
const result = daysInMonth(month, year)
assert.equal(result, 29)
})
})
The daysInMonth function can be implement using isLeapYear as follows:
function daysInMonth(month, year) {
const longMonths = [1, 3, 5, 7, 8, 10, 12]
if(longMonths.includes(month)) {
return 31
} else if(month === 2) {
if(isLeapYear(year)) {
return 29
} else {
return 28
}
} else {
return 30
}
}
Now, we can write a function that determines the next day given any date. This function would use the daysInMonth function to determine when to increment the month. We can use the following tests:
describe("next day examples", () => {
it("returns the next day for 2/29/2020", () => {
const date = [2020, 2, 29]
const output = [2020, 3, 1]
const result = nextDay(date)
assert.deepEqual(result, output)
}) it("returns the next day for 12/31/2019", () => {
const date = [2019, 12, 31]
const output = [2020, 1, 1]
const result = nextDay(date)
assert.deepEqual(result, output)
})
})
The nextDay function can be implemented as follows using the daysInMonth function:
function nextDay(date) {
let year = date[0]
let month = date[1]
let day = date[2] if (day < daysInMonth(month, year)) {
day += 1
} else {
if (month < 12) {
month += 1
day = 1
} else {
year += 1
month = 1
day = 1
}
}
return [year, month, day]
}
Finally, the days in between two days can be determined using the nextDay function. While the first date is before the second date, the first date is updated to the next date and a day is added to the counter. The .skip is removed from the associated testing suite and the tests are passed.
function daysBetweenDates(date1, date2) {
let days = 0
while (isDateBefore(date1, date2)) {
date1 = nextDay(date1)
days += 1
}
return days
}
Conclusions
The Mocha testing framework is fairly straight forward to set up and can be used to break down a problem in a structured systematic way. I would like to acknowledge Dave Evans and the Udacity Data Structures and Algorithms course from which I developed this example.