Testing with Jest

Testing with Jest

What is Jest?

It is a javascript testing framework, which is easy to use and understand. It works with node, react, angular, babel and much more. Easy to set up and use, along with precise error messages which help to write error-free tests.

Installation

The naming of these test files is given as follows name.test.js, add the following script in your package.json and now we can run the test by using npm run test command.

{
  "scripts": {
    "test": "jest"
  }
}

Suppose we have functions defined in a file.

function sum(a, b) {
  return a + b;
}
module.exports = sum;

Now we will see how can we test them. First of all, we will import them into our test file.

const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

Terminologies -

  1. describe block - It helps group related tests together under a common heading, making it easier to understand and manage tests.

     describe('Family Photos', () => {
       test('Photo of mom', () => {
         // This is a specific test (photo) for your mom.
       });
       test('Photo of dad', () => {
         // This is a specific test (photo) for your dad.
       });
     });
    
  2. test block - a way to group a specific piece of testing logic.

     test('description of what this test does', () => {
         // The actual testing logic goes here
     });
    
  3. expect - expect(something).matcher(value);

    • something is the actual value or result produced by your code.

    • matcher is a method that tells Jest how to compare something with the expected value.

    expect(2 + 2).toBe(4);

Basics of jest -

The primary block you use to define a test in Jest is the test function, though it is also frequently used as an alias. Inside these blocks, you write your assertions using the expect function combined with various matches.

  1. Matchers -

     // truthiness
     test('null', () => {
       const n = null;
       expect(n).toBeNull();
       expect(n).toBeDefined();
       expect(n).not.toBeUndefined();
       expect(n).not.toBeTruthy();
       expect(n).toBeFalsy();
     });
    
     // numbers
     test('numbers', () => {
       const valu = 5 + 5;
       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(10);
       expect(value).toEqual(10);
     });
    
     // strings using regex
     test('there is no I in team', () => {
       expect('team').not.toMatch(/I/);
     });
    
     // arrays 
     const arr = [1,2,3];
     test("check",()=>{
         expect(arr).toContain(2);
     })
    
     //exceptions
     function throwError() {
         throw new Error("error thrown")
     }
     test("running",()=>{
         expect(()=>throwError()).toThrow();
     })
    
  2. Testing async code -

     // promises - let's say fetchD is a promise which fetches data
     test("test promise",()=>{
         return fetchD().then(d=>{
             expect(d).toBe("data")
         })
     })
    
     // async/await - let's say fetchD is a async function now
     test('test async', async () => {
       const data = await fetchD();
       expect(data).toBe("data");
     });
    
     // callbacks - done keyword Jest will wait until 
     // the done callback is called before finishing the test.
     test('the data is peanut butter', done => {
       function callback(error, data) {
         if (error) {
           done(error);
           return;
         }
         try {
           expect(data).toBe('peanut butter');
           done();
         } catch (error) {
           done(error);
         }
       }
       fetchData(callback);
     });
    
     // resolves/reject
     test('resolve and reject', () => {
       return expect(fetchD()).resolves.toBe('data');
     });
    
  3. Setup and teardown -

     // repeating things need to execute for test rather executing it
     // again and again we will put it inside a block 
    
     // will run before each test
     beforeEach(() => {
       initializeCityDatabase();
     });
    
     // will run after each test 
     afterEach(() => {
       clearCityDatabase();
     });
    
     // one time setup
    
     // will run before all tests execute
     beforeAll(() => {
       return initializeCityDatabase();
     });
    
     // will run after all tests has been executed
     afterAll(() => {
       return clearCityDatabase();
     });
    

    The top level before* and after* hooks apply to every test in a file. The hooks declared inside a describe block apply only to the tests within that describe block. This is what is known as scoping.

    Talking about the order of execution, jest first executes describe block before actually running any of the tests inside it. Once the describe blocks are complete, by default Jest runs all the tests serially in the order they were encountered.

Mock functions - In code testing, sometimes we have parts of our code (often called "dependencies") that aren't the main focus of our test or might cause complications if used directly (like making actual network requests, interacting with a database, etc.). Instead of using the real thing, we use a "mock function" as a stand-in. This mock function will mimic the behavior of the real function but in a controlled manner.

You will understand it better from the docs itself.

Mock functions


In case you need an easier example and explanation for specific things in jest you can comment below for the same.