Testing with Jest

faminiprodev

Famini-ProDev

Posted on April 23, 2022

Testing with Jest

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
:

Image description

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.

Image description

To run the test, use the script

npm run test
Enter fullscreen mode Exit fullscreen mode

This will look for the test script mentioned in the package.json of the project.

Image description

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);
});
Enter fullscreen mode Exit fullscreen mode

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'.

Image description

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);
 });
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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" });
});
Enter fullscreen mode Exit fullscreen mode

Running the above test will 'Pass'.

Image description

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);
});
Enter fullscreen mode Exit fullscreen mode

This test demonstrates the deep-matching feature of toEqual. 
The above test passes as every key-pair in gfgObj matches with testObj.

Image description

*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
Enter fullscreen mode Exit fullscreen mode

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);
})
Enter fullscreen mode Exit fullscreen mode

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
})
Enter fullscreen mode Exit fullscreen mode

The above test passes.
Image description

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.

Image description
** Matching Numbers:**

//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
})
Enter fullscreen mode Exit fullscreen mode

Image description
**
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']));
})
Enter fullscreen mode Exit fullscreen mode

Th above test passes as user1 and user3 are present in gfgUsers.
Image description

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']));
}) 
Enter fullscreen mode Exit fullscreen mode

Image description

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'
}) 
Enter fullscreen mode Exit fullscreen mode

Image description

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,
      };
    }
},
});
Enter fullscreen mode Exit fullscreen mode

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');
})
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

Reference:

💖 💪 🙅 🚩
faminiprodev
Famini-ProDev

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