Testing with Jest
Famini-ProDev
Posted on April 23, 2022
Jest is a Javascript Testing Framework by Facebook. It is used most commonly for unit testing. Unit testing is when you provide input to a unit of code(usually, a function) and match the output with the expected output.
Jest Features:
- zero config: As we will see later in this article, close to none configuration is required to get started with writing tests and deploying them. However, a config file can be supplied to the test suite as well.
- snapshots: Jest has the ability to enable snapshot testing as well. Essentially, the snapshots are matched with the saved snapshot and check for matching functionality.
- isolated tests: Jest tests are run parallelly to improve run time.
Setting up a jest project
- Install Jest using npm
:
Project Structure:
In the project root directory, make a tests folder. This folder will store all the test files.
Note that the js files(which are to be tested) are mapped by their names.
For example, index.js is mapped to index.test.js. This index.test.js file is placed in the 'tests' folder. This is the conventional project structure.
Start testing:
To start, lets see a basic test workflow.
To run the test, use the script
npm run test
This will look for the test script mentioned in the package.json of the project.
We will use the 'expect' method to test our functions. The functions can also be tested using 'describe' and 'it'.
A basic test: adding two positive nums and checking result.
//index.js
testForAdd: (a, b) => { return (a + b) },
//index.test.js
test('test adding two positive nums', function() {
expect(testFns.testForAdd(4, 5)).toBe(9);
});
When the 'npm run test
' is run, the index.test.js file is gone through. Then the testForAdd _function is run which is placed in the '_testFns' object. The toBe is used to 'match' the returned response from the test to what is expected. This 'result matching' leads to either a 'fail' or a 'pass'.
The following test will fail because of the 'toBe(8)'.
//example of a failing test
test('test adding two positive nums - failing test', function() {
expect(testFns.testForAdd(4, 5)).toBe(8);
});
Opposite of toBe: not.toBe()
The opposite of the 'toBe' matcher is created by simply prefixing it with 'not.'
For example:
//test successful - test for opposite of a matcher.
//The following test will 'Pass' if the returned value is not equal to 8.
test('test adding two positive nums - failing test', function() {
expect(testFns.testForAdd(4, 5)).not.toBe(8);
});
Using 'toBe' with JS Objects:
Let's think of a case where every field of a JS object is to be tested. Jest provides us a way to do this using 'toEqual'. The 'toEqual' is a deep-matcher(checks every field and sub-fields possible).
//expect toEqual example - check every field's value
// testFns.test_toEqual(gfgObj)
test('check gfgObj toEqual', () => {
let gfgObj = { name: "GFG" };
gfgObj['type'] = "company";
expect(gfgObj).toEqual({ name: "GFG", type: "company" });
});
Running the above test will 'Pass'.
Another variation of doing this is matching two objects using the 'toEqual
'.
This is done so like this :
test('check gfgObj toEqual', () => {
let gfgObj = {
name: "GFG",
type: "company",
members: {
employees: 100,
contributors: 500
}
};
let testObj = {
name: "GFG",
type: "company",
members: {
employees: 100,
contributors: 500
}
};
expect(gfgObj).toEqual(testObj);
});
This test demonstrates the deep-matching feature of toEqual.
The above test passes as every key-pair in gfgObj matches with testObj.
*toBeCloseTo - for floating point numbers and other approximate matches *
//see here that a+b != c even though simple addition is taking place.
> var a = 1.32
> undefined
> var b = 2.31
> undefined
> c = a+b;
> 3.63
> var res = false;
> if(c==(a+b)) {c=true;}
> undefined
> c
> false
In such conditions, it is good to use 'toBeCloseTo
' matcher from the Jest library.
test('floating point number - use toBeCloseTo instead', function() {
// const num1 = 0.3;
// const num2 = 0.2;
const result = 9.31 + 9.2;
expect(result).toBeCloseTo(18.51);
})
The above test passes too.
matching truthy and falsy:
https://jestjs.io/docs/en/using-matchers#truthiness
A lot of times, tests are written to check for truthy and falsy values returned by 'expect'.
**Falsy **values in JS.
**Truthy **values in JS.
//checking for truthy values - All the tests will return truthy.
test('check for truthy', function() {
const gfgObj = {
first: null,
second: undefined,
third: false
}
expect(gfgObj.first).not.toBeTruthy(); // True - Pass
expect(gfgObj.second).toBeUndefined(); // True - Pass
expect(gfgObj.third).toBeFalsy(); // True - Pass
})
However, if any of the 'expect' above fails, Jest returns **meaningful **error **messages **like below.
**Note **that in above case, if any of the 'expect'-s fail, the test also completely fails.
//tests for Number matches
test('test for numbers', function() {
const result = 3 + 9;
// expect(result).toBe(12); //the plain old matcher
expect(result).not.toBeLessThan(10); // result > 10
expect(result).toBeLessThan(15); // result < 15
expect(result).not.toBeGreaterThan(15); // result 10
expect(result).toBeGreaterThanOrEqual(12); //result >= 12
// expect(result).not.toBeGreaterThanOrEqual(12); // result == 12, this Fails
// expect(result).toBeLessThanOrEqual(10); // result >= 10, this Fails
})
**
Testing values contained in arrays: **
We can also test if particular values are contained in an array. Note that this test will 'Fail' if at least one value is not present in the array. For example,
//testing arrays
const gfgUsers = [
'user1',
'user2',
'user3'
];
test('test for a value in gfgUsers', function() {
// expect(gfgUsers).toContain('user2');
// expect(gfgUsers).not.toContain('user2');
//expect array containing
expect(gfgUsers).toEqual(expect.arrayContaining(['user1', 'user3']));
})
Th above test passes as user1 and user3 are present in gfgUsers.
However, the following test will fail because 'user4' is not present in gfgUsers.
//testing arrays
const gfgUsers = [
'user1',
'user2',
'user3'
];
test('test for a value in gfgUsers', function() {
//expect array containing
expect(gfgUsers).toEqual(expect.arrayContaining(['user1', 'user4']));
})
test('string match tests - toMatch - used for regex-es', function() {
const str = 'GeeksforGeeks';
// expect(str).toMatch(/f/);
// expect(str).toMatch(/Z/);
//you can create more complex Regexes
const str1 = 'This is a test paragraph. I wrote it.'
expect(str1).toMatch(/[pP][hH][\.]/); //matches 'ph.' in the word 'paragraph'
})
Extending the Matchers
Jest also has the provision to extend its 'Matchers' functionality, which is accomplished using the 'expect.extend()'
keyword. The .extend() function is passed matchers as objects.
Syntax: expect.extend({matcher1, matcher2})
;
For example, if we want to build a matcher that checks for a phrase presence in a string:
expect.extend({
stringPresent(receivedString, phraseString) {
bool phrasePresent = true;
var re = new RegExp(phraseString);
if (re.test(receivedString)) {
phrasePresent = true;
} else {
phrasePresent = false;
}
if (phrasePresent === true) {
return {
message: () =>
`string present`,
pass: true,
};
} else {
return {
message: () =>
`string absent`,
pass: false,
};
}
},
});
Dealing with exceptions
We can also check the types of errors that a unit of code throws. We can check the error thrown by name, message, object, etc.
Syntax:expect( fnName() ).toThrow( error )
;
The error parameter/argument is optional here.
Let's suppose that we want to test a function by the message of the error thrown.
function testGFGMessage() {
throw new Error('this is testGFGMessage function error');
}
test('testing testGFGMessage', function(){
expect(testGFGMessage).toThrow('this is testGFGMessage function error');
})
There are many other ways to throw Errors and check for them. A detailed reference can be found here.
Skipping/Running a subset of tests
https://jestjs.io/docs/en/api#testskipname-fn
Jest also has a provision for skipping specific tests while running the test suite.
To implement it, simply use the 'skip' keyword. For example,
function addFn(num1, num2){
return num1 + num2;
}
test.skip('skip test example - skipping the addition test', () => {
expect(addFn(2, 3)).toBe(5);
});
The opposite of this is implementing only a subset of tests, which is achieved by using the 'only' keyword. For example,
function addFn(num1, num2){
return num1 + num2;
}
test.only('skip test example - skipping the addition test', () => {
expect(addFn(2, 3)).toBe(5);
});
Posted on April 23, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 11, 2024