Jest For Everyone: The Basics
Source: jestjs.io
Before continuing this part please read this one first.
In the previous part of the tutorial, we wrote our first test. In this part, we are going to learn the basic of Jest to write better tests by following real-world scenarios.
If you remember about the part-1. We write tests for sayHi
function.
1const sayHi = require('../sayHi.js');23test('should sayHi to John', () => {4 const result = sayHi('John');5 const expected = 'Hi John!';67 expect(result).toBe(expected);8});
In this test, the most important line is 7 expect(result).toBe(expected);
. Here we tested our function.
In sayHI
function returns string
. But when we are going to build an application there will not only be a function that will return a string. There will be boolean
, array
, object
, promise
, and exception
(error). How can we test them?
Jest provides us necessary matcher
to test those kinds of values. Let's learn them.
What is a matcher?
Matcher gives you different ways to test your expected value. There are many matchers available. Some of them are common and some of them are specific to specific kinds of data like the object, or arrays.
Common matchers
Did notice something? In our first test, we used toBe
function. This is a common matcher. Using a common matcher we can test anything. There are more common matchers toEqual
, toStrictEqual
.
Let's rewrite our first test (sayHi
) using these common matchers.
1const sayHi = require('../src/sayHi.js');23test('should say hi to John', () => {4 const result = sayHi('John');5 const expected = 'Hi John!';67 expect(result).toEqual(expected);8 expect(result).toStrictEqual(expected);9});
Run the test, it will pass without giving any errors.
1test('common matcher example with primitive values', () => {2 const result = 6;3 const expected = 6;45 expect(result).toBe(expected);6 expect(result).toEqual(expected);7 expect(result).toStrictEqual(expected);89 expect(true).toBe(true);10 expect(false).toEqual(false);11 expect(null).toStrictEqual(null);12});
This test will also be passed. You can see using those matchers we can test both number
, boolean
, and string
.
1test('common matcher example with array value (tobe)', () => {2 expect([]).toBe([]); // will fail3});45test('common matcher example with array value (toEqual)', () => {6 expect([]).toEqual([]);7});89test('common matcher example with array value (toStrictEqual)', () => {10 expect([]).toStrictEqual([]);11});
Notice for the same value []
they produced a different result. toBe
failed and others passed. Why?
Because toBe
uses Object.is
to test equality. This means checks value and references. Two empty arrays reference are not the same, they are different. That's why it fails. But toEqual
and toStrictEqual
only check for value not for the reference that why they are passed.
1const obj1 = {2 name: 'John',3 age: 25,4 address: undefined,5};67const obj2 = {8 name: 'John',9 age: 25,10};1112test('common matcher example with object values', () => {13 expect(obj1).toEqual(obj2);14});1516test('common matcher example with object values', () => {17 expect(obj1).toStrictEqual(obj2);18});
Again different result! toEqual
passed and toStrictEqual
failed. Why?
When toEqual
compares objects, it ignores undefined properties, undefined array items, array sparseness, and object type mismatch, But toStrictEqual
don't.
This common matcher works well with primitive
values, but be careful when testing non-primitive/reference
values. Use the most suitable one based on your requirement.
Let's learn about the other available matchers.
String
Let's extend our sayHi
function. Now our sayHi
function will return Hi
, Hello
, Hey
randomly instead of Hi
only.
1function extendedSayHi(name) {2 const greetings = Math.random() * 10 > 5 ? 'Hello' : 'Hi';34 return `${greetings} ${name}!`;5}67module.exports = extendedSayHi;
Let's write test for this.
1const extendedSayHi = require('../src/extendedSayHi.js');23test('should say hi to John', () => {4 const result = extendedSayHi('John');5 const expected = 'Hi John!';67 expect(result).toBe(expected);8});
Can you find an error on the above test?
Actually, the above test sometimes will pass and sometimes will fail. Because extendedSayHi
will return Hi
or Hello
based on the random value. This means that our returned value will not be the same always. So, we can't use common matchers, then how can we test that?
In this condition, we can use toMatch
Matcher.
1const extendedSayHi = require('../src/extendedSayHi.js');23test('should say hi to John', () => {4 const result = extendedSayHi('John');56 expect(result).toMatch(/(hi|hello)\\s+jhon/i);7});
We have tested our extendedSayHi
function. In toMatch
function, we passed regular expression. Using toMatch
we can test string values.
toMatch(regexp | string)
- to check that a string matches a regular expression.
Number
Let's write a floatSum
function. floatSum
function will take two float numbers as arguments and will return the sum of the numbers.
1function floatSum(num1, num2) {2 return num1 + num2;3}45module.exports = floatSum;
Let's write the test for floatSum
function.
1const floatSum = require('../src/floatSum.js');23test('0.3 + 0.3 should be equal to 0.6', () => {4 const result = floatSum(0.2, 0.1);56 expect(result).toBe(0.3);7});
Test failing, Everything seems ok. What's wrong? It is failing because of JavaScript's wired behavior. The function doesn't actually return 0.3
. Let's update it.
1const floatSum = require('../src/floatSum.js');23test('0.3 + 0.3 should be equal to 0.6', () => {4 const result = floatSum(0.2, 0.1);56 expect(result).toBeCloseTo(0.3);7});
Now we are not testing the exact value, testing the approximate close value with the help of toBeCloseTo
. There are more matchers available to test number values you can explore from Jest docs.
toBeCloseTo(number, numDigits?)
- to compare floating point numbers for approximate equality.
Boolean
We saw earlier we can test boolean
values using common matchers.
1expect(true).toBe(true);2expect(false).toEqual(false);
Now look at the function below.
1const willReturnTruthyValue = () => {2 const val = Math.random(3);34 if (val < 1) {5 return 1;6 }7 if (val < 2) {8 return true;9 }10 if (val < 0) {11 return 'nothing';12 }13};
How can we test this function? Do you have any idea? This function is not consistant with the return value. But look carefully it always returns truthy
values.
1it('should check the truthy value', () => {2 expect(willReturnTruthyValue()).toBeTruthy();3});
Now we are checking for truthy values. Now look at another similar example with a falsy value.
1const willReturnFalsyValue = () => {2 const val = Math.random(3);3 if (val < 1) {4 return 0;5 }6 if (val < 2) {7 return false;8 }9 if (val < 0) {10 return '';11 }12};
To test this function/scenario, there is toBeFalsy
matcher available.
1it('should check the falsy value', () => {2 expect(willReturnFalsyValue()).toBeFalsy();3});
In this kind of scenario when only care about them is true or false. We can use toBeTruthy
and toBeFalsy
to test our function.
toBeTruthy
- when you don't care what a value is and you want to ensure a value is true in a boolean context.toBeFalsy
- when you don't care what a value is and you want to ensure a value is false in a boolean context.
Array
Imagine, we have a database, where we store our favorite book list. To store books in the database we are using addFavBook
function.
1const mockDB = [];23function addFavBook(book) {4 mockDB.push(book);5}
Now we want to test our function, is adding the book to the list or not?
1const { mockDB, addFavBook } = require('../src/addFavBook');23it('should add new book to the list', () => {4 const newBook = 'Show your work';56 addFavBook(newBook);78 expect(mockDB).toContain(newBook);9});
Our function working properly. Using toContain
we are testing whether our new book was added or not in the mockDB. There is more matcher to test this kind of scenario like toHaveLength
, toContainEqaul
.
toHaveLength(number)
- to check that an object has a.length
property and it is set to a certain numeric value.toContain(item)
- to check that an item is in an array (for primitives)
Object
Now think about the common scenario, A user wants to signup on to our app. In this case, the frontend sends data user data to the backend. The backend does the operation to the database and returns a new user object with id and access token (it may vary, I am just saying this for the example purpose).
1const addUser = userObj => {2 // save the user in the database3 return {4 _id: Math.random(10),5 accessToken: `Token_${Math.random()}`,6 ...userObj,7 };8};
Lets test this function.
1const addUser = require('../src/addUser');23test('should add new user', () => {4 const user = {5 name: 'Chester Bennington',6 profession: 'Singer',7 };8 // adding the user9 const result = addUser(user);1011 expect(result).toHaveProperty('_id');12 expect(result).toHaveProperty('accessToken');13 expect(result).toMatchObject(user);14});
First checking the result whether we are getting the extra properties or not (_id
, accessToken
). Then checking whether our passed object matched the return object or not. We can test using these matchers which function returns object
.
toHaveProperty(key, value?)
- To check if the property at provided referencekey
exists for an object.toMatchObject(object)
- To check if an object matches a subset of the properties of an object.
Promise
The previous two examples show DB operation. But we DB operation happens we can't get the normal return value because it async operation. It should return the promise
value. If a function return promise
how can we test them?
1const promise = error => {2 return new Promise((resolve, reject) => {3 setTimeout(() => {4 resolve('promise resolved'); //5 }, 2000);6 });7};
Let's write the test for this function.
1const promiseFunc = require('../src/promiseFunc.js');23test('should test promise value', () => {4 expect(promiseFunc()).toMatch(/promise resolved/i); // will fail5});
We can't write tests as we did earlier because it is promise
value. We need to use then/catch
or async/await
to test this.
1const promiseFunc = require('../src/promiseFunc.js');23test('should test promise value', () => {4 return promiseFunc().then(data => {5 expect(data).toMatch(/promise resolved/i);6 });7});
There is one thing to notice, if we don't add return
it will not work. Keep this in your mind.
1test('should test promise value', async () => {2 const result = await promiseFunc();3 expect(result).toMatch(/promise resolved/i);4});
We can also use this instead of the above example. This is how we can test promise values.
Error
Till now we have tested every function that runs without any error. What if a function throws an error, how can we test that function?
1const willThrowError = () => {2 throw new Error('This is a funny error.');3};
Let's learn how to test this scenario.
1it('should check throw error or not', () => {2 expect(() => willThrowError()).toThrow();3 expect(() => willThrowError()).toThrow(/funny error/i);4});
toThrow
ensure that a function throws an error or not. Did you notice something different in this test?
We are calling willThrowError
function inside another function. Why? If we don't wrap with function the test will fail. So, keep in this mind when testing this scenario.
toThrow(error?)
- to test that a function throws when it is called.
Exception
There is a bonus part. We can test null
, undefined
, and NaN
using a common matcher.
1const willReturnNaN = () => NaN;2const willReturnNull = () => null;3const willReturnUndefined = () => undefined;45it('should return null value', () => {6 expect(willReturnNull()).toBe(null);7});89it('should return undefined value', () => {10 expect(willReturnUndefined()).toBe(undefined);11});1213it('should return NaN value', () => {14 expect(willReturnNaN()).toBe(NaN);15});
But there are better matcher tests this kind of scenarios.
1const willReturnNaN = () => NaN;2const willReturnNull = () => null;3const willReturnUndefined = () => undefined;45it('should return null value', () => {6 expect(willReturnNull()).toBeNull();7});89it('should return undefined value', () => {10 expect(willReturnUndefined()).toBeUndefined();11});1213it('should return NaN value', () => {14 expect(willReturnNaN()).toBeNaN();15});
Summary
Now we know how to use matchers
to test different kinds of values. It is time to practice what we know. I highly encourage you to find some open-source projects and write tests for those projects. Thanks for your effort and time to read this article. Happy testing!
See you in the next part, next part we learn about modifiers
, mock
, and spy
.
You can get the example code from my github repo (branch: part-2).