An Insight to Jest
A guide on what Jest is, why we need it and how to implement it
By Dharshinisivakumar, a social butterfly!
Unit testing is an integral part of Test Driven Development (TDD) where we define the actions of a function and its expected result even before working on the actual function!
The most common question in mind!
My code works fine without TDD, then why?
There are some hidden perks of incorporating TDD into our development process
2. TDD stands by the proverb Time is precious , thus saves the development time.
3. Definitely improves the quality of code.
Guess you have changed your mind now ! Let’s get into knowing about jest.
Every programming language has its own framework for unit tests. For JavaScript , Jest is one of the widely used testing frameworks, maintained by Facebook, Inc. Jest is also known for its simplicity !
Let us assume that the following code is written in
addition.js
function add(a,b)
{
return a+b
}
module.exports = add
Test cases for the same are written in a file named addition.test.js for Jest to automatically pick it up.
addition.test.js
const addition = require(‘./addition’)
describe(“testing the sum of two numbers”,() =>
{
it(“pass”,() => {
const expectedOutput = 50;
expect(addition(25,25)).toEqual(expectedOutput);
})
it(“pass case 2”,()=>{
const wrongOutput = 511;
expect(addition(4,4)).not.toEqual(wrongOutput)
})
})
That was really simple isn’t it ?
1. The installation step in jest is pretty simple.
The installation can be done with either npm or yarn
2. Jest is fast. Very fast.
When your tests are CPU bound, it can save significant time from your test runs.
3. Jest has awesome mocks! Mocking allows us to reduce irrelevant dependencies and reduces the lines of code and also makes the test cases run faster. We have discussed more about mocks later in this blog.
4. Jest does snapshot testing!
It lets you capture a string that represents your rendered component and store it in a file. Then you can compare it later to ensure that the UI didn’t change. If you actually change your UI then you need to update your snapshot files to reflect it of course.
5. Jest watches you all the time!
When you run your test cases with — watchAll command line argument. Jest can run in watch mode. Whenever your code is changed the test cases run automatically to test the new changes.
1. Simple Mock Functions
Unit tests and mocking are inseparable. If your code does operations on a file, calls remote service, access a database, it will be complicated to configure these in your tests. In such cases Mocks are our superheroes. We can replace the real dependency with mock function that does nothing but just records the fact it was called, so you can verify the workflow. The jest.fn() mock function lets you provide canned return values
Sample code
For a function like
function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}
Mock functions can be used
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);
// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);
2. Manual Module Mocks
Sometimes you may need to replace a whole module with its data rather than a couple of functions. Jest lets you do that by placing your own module with the same name in a __mocks__ sub-directory. The syntax of manual mock is jest.mock(moduleName, factory, options)
Whenever your code is using the target module, it will access your mock rather than the real module. You can even selectively choose for some tests to use the original module by calling jest.Unmock(‘moduleName’).
Sample code
Lets see an example of a module which provides the summary of all files in a directory
FileSummarizer.js
const fs = require(‘fs’);
function summarizeFilesInDirectorySync(directory) {
return fs.readdirSync(directory).map(fileName => ({
directory,
fileName,
}));
}
exports.summarizeFilesInDirectorySync = summarizeFilesInDirectorySync;
To prevent our tests from actually hitting the disk (that’s pretty slow), we will be creating a manual mock for the file system module by extending an automatic mock. Our manual mock will implement custom versions of the above APIs.
__mocks__/fs.js
const path = require(‘path’);
const fs = jest.createMockFromModule(‘fs’);
// This is a custom function that our tests can use during setup to specify
// what the files on the “mock” filesystem should look like when any of the
// `fs` APIs are used.
let mockFiles = Object.create(null);
function __setMockFiles(newMockFiles) {
mockFiles = Object.create(null);
for (const file in newMockFiles) {
const dir = path.dirname(file);
if (!mockFiles[dir]) {
mockFiles[dir] = [];
}
mockFiles[dir].push(path.basename(file));
}
}
// A custom version of `readdirSync` that reads from the special mocked out
// file list set via __setMockFiles
function readdirSync(directoryPath) {
return mockFiles[directoryPath] || [];
}
fs.__setMockFiles = __setMockFiles;
fs.readdirSync = readdirSync;
module.exports = fs;
Now the test comes into picture
FileSummarizer.test.js
jest.mock(‘fs’);
describe(‘listFilesInDirectorySync’, () => {
const MOCK_FILE_INFO = {
‘/path/to/file1.js’: ‘console.log(“file1 contents”);’,
‘/path/to/file2.txt’: ‘file2 contents’,
};
beforeEach(() => {
// Set up some mocked out file info before each test
require(‘fs’).__setMockFiles(MOCK_FILE_INFO);
});
test(‘includes all files in the directory in the summary’, () => {
const FileSummarizer = require(‘../FileSummarizer’);
const fileSummary =
FileSummarizer.summarizeFilesInDirectorySync(‘/path/to’);
expect(fileSummary.length).toBe(2);
});
});
In the above code the line
jest.mock(‘fs’);
instructs our tests to mock the fs module we have created.
3. Timer Mocks
Timing is the scourge of unit tests. What if you want to test code that times out after a minute? Code that fires every 30 seconds? Special code that runs an algorithm at the end of the month?
Those are difficult to test. You can either succumb to the timing requirements of the original code (and then your tests will be very slow), or you can manipulate time, which is much more useful. Jest lets you control the following timer-related functions:
Sample code
mainfile.js
function timerGame(callback) {
console.log(‘Ready….go!’);
setTimeout(() => {
console.log(“Time’s up — stop!”);
callback && callback();
}, 1000);
}
module.exports = timerGame;
mainfile.test.js
jest.useFakeTimers();
jest.spyOn(global, ‘setTimeout’);
test(‘waits 1 second before ending the game’, () => {
const timerGame = require(‘../timerGame’);
timerGame();
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
});
In a nutshell, Jest is an entrancing testing framework with a simple installation procedure and an elegant testing approach and also it comes with a rich mocking library, which makes testing enjoyable and faster. Jest also helps in maximum code coverage. Most importantly jest creates a developer friendly environment for testing. To know more about this awesome testing tool, visit the official site.
Happy testing & jesting!
We at CaratLane are solving some of the most intriguing challenges to make our mark in the relatively uncharted omnichannel jewellery industry. If you are interested in tackling such obstacles, feel free to send in your updated resume/CV to careers@caratlane.com.
Leave a Reply