Table of contents
Introduction
In the fast-paced world of software development, ensuring the quality of code is essential for delivering robust and reliable applications. Unit testing plays a pivotal role in this process, allowing developers to validate individual code components in isolation.
In this blog, we will explore the significance of unit testing and delve into the capabilities of Jest, highlighting why it has become a go-to tool for developers.
Unit Testing - isn’t it a tester’s job?
Well, the answer is No, Unit testing is primarily for developers because it is a testing approach focused on verifying the correctness and reliability of individual units or components of a software application.
What is Jest?
Among the numerous testing frameworks available, Jest has emerged as a popular choice due to its simplicity, speed, and extensive feature set. Jest is an open-source JavaScript testing framework developed by Facebook. It is specifically designed for testing React applications but can be used for any JavaScript codebase.
Jest documentation → https://jestjs.io/docs/getting-started
Getting Started
Installation
Use npm or yarn to install Jest as a development dependency in your project.
npm install -D jest
This package is only for use in development therefore use -D
Configuration
Add the following section to your package.json
:
{
"scripts": {
"test": "jest"
}
}
Run watch mode:
jest --watch #runs jest -o by default
jest --watchAll #runs all tests
Writing your first test
Tests can be defined using it
or test
. I prefer to use it
. You can choose your way.
//add.js
const add = (a, b) => a + b;
module.exports = add;
// add.test.js <- It is a Test file with test.js extension
const add = require("./add");
it("should add two numbers", () => {
expect(add(1, 2)).toBe(3);
});
Run the test
Run the command npm test
in your terminal
# Output
PASS ./add.test.js
√ should add two numbers (2 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.449 s
Ran all test suites.
Using Matchers
Expect
expect(value)
: Theexpect
function accepts a single argument, which is the value you want to make assertions about.It gives you access to a number of
matchers
that allow you to validate different thingsexpect(result).toBe(expected); expect(array).toHaveLength(length); expect(value).toBeTruthy();
Modifiers
.not
: Lets you test its oppositeexpect(value).not.toBeTruthy();
.resolves
: Use resolves to unwrap the value of a fulfilled promise so any other matcher can be chained. If the promise is rejected the assertion fails. More detail → hereit('should resolves to lemon', () => { // make sure to add a return statement return expect(Promise.resolve('lemon')).resolves.toBe('lemon'); });
.rejects
: Use.rejects
to unwrap the reason of a rejected promise so any other matcher can be chained. If the promise is fulfilled the assertion fails. More detail → hereit('should rejects to octopus', () => { // make sure to add a return statement return expect(Promise.reject(new Error('octopus'))).rejects.toThrow( 'octopus', ); });
Commonly used Matchers
Matchers are set functions used to compare the test value. See list here
Simple Assertions
Use expect()
methods chained with toBe()
method. It accepts a value as a matcher
it("should add 2 with 2", () => {
expect(2 + 2).toBe(4);
})
Object / Array Assertion
Use expect()
method chained with toEqual()
method. It accepts an object or array as a matcher
// Proper way
it('should assign new key value pair in object', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
});
// Best way
it('should assign new key value pair in object', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toMatchObject({one: 1, two: 2});
});
toEqual
ignores object keys withundefined
properties,undefined
array items, array sparseness, or object type mismatch. usetoStrictEqual
instead.
Truthiness
You sometimes need to distinguish between undefined
, null
, and false
toBeNull
matches onlynull
toBeUndefined
matches onlyundefined
toBeDefined
is the opposite oftoBeUndefined
toBeTruthy
matches anything that anif
statement treats as truetoBeFalsy
matches anything that anif
statement treats as false
it('should be null', () => {
const n = null;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
});
// Output
√ null (3 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
You should use the matcher that most precisely corresponds to what you want your code to be doing.
Numbers
Most ways of comparing numbers have matcher equivalents.
.toBe(value)
.toEqual(value/object)
.toBeGreaterThan(num)
.toBeGreaterThanOrEqual(num)
.toBeLessThan(num)
.toBeLessThanOrEqual(num)
.toBeCloseTo(float)
it('should check two numbers', () => {
const value = 2 + 2;
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4.5);
// toBe and toEqual are equivalent for numbers
expect(value).toBe(4);
expect(value).toEqual(4);
});
it('should add floating point numbers', () => {
const value = 0.1 + 0.2;
//expect(value).toBe(0.3); This won't work because of rounding error
expect(value).toBeCloseTo(0.3); // This works.
});
Strings
You can check strings against regular expressions with toMatch
it('there is no I in team', () => {
expect('team').not.toMatch(/I/);
});
it('but there is a "stop" in Christoph', () => {
expect('Christoph').toMatch(/stop/);
});
Arrays and iterables
You can check if an array or iterable contains a particular item using toContain
const shoppingList = [
'diapers',
'kleenex',
'trash bags',
'paper towels',
'milk',
];
// Proper way
test('the shopping list has milk on it', () => {
expect(shoppingList).toContain('milk');
expect(new Set(shoppingList)).toContain('milk');
});
// Best way
test("the shopping list has milk on it", () => {
expect(shoppingList).toEqual(expect.arrayContaining(["milk", "diapers"]));
});
Exceptions
If you want to test whether a particular function throws an error when it's called, use toThrow
function compileAndroidCode() {
throw new Error('you are using the wrong JDK!');
}
test('compiling android goes as expected', () => {
expect(() => compileAndroidCode()).toThrow();
expect(() => compileAndroidCode()).toThrow(Error);
// You can also use a string that must be contained in the error message or a regexp
expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
expect(() => compileAndroidCode()).toThrow(/JDK/);
// Or you can match an exact error message using a regexp like below
expect(() => compileAndroidCode()).toThrow(/^you are using the wrong JDK$/); // Test fails
expect(() => compileAndroidCode()).toThrow(/^you are using the wrong JDK!$/); // Test pass
});
The function that throws an exception needs to be invoked within a wrapping function otherwise the
toThrow
assertion will fail.
That’s it for this blog. There are a lot of topics to cover such as before, after Test Methods, Setup, Order of Execution and Mock functions which I will be covering in part 2 of this blog.
Wrapup 🥱
Thanks for reading! I really hope you enjoyed reading this short blog. If you are also a passionate developer like me then please give it a thumps-up that will keep me motivated.
If you have any query/suggetions/feedback please connect with me on twitter or ask me in the comment section.
Like and Follow 😊
Meet you on the next blog. Enjoy Coding ❤