Table of contents
1.
Introduction
2.
Callbacks
3.
Promises
4.
.resolves/.rejects
5.
Async/Await
6.
Example
7.
FAQs
8.
Key Takeaways
Last Updated: Mar 27, 2024

Asynchronous Testing

Author Gunjan Ahuja
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

While working with javascript, the code has become common to run asynchronously. So, how will Jest know whether the test is completed or not so that it can move to the next step? Therefore, it is essential to comprehend asynchronous testing to understand when to move to the next test when the previous one is done. The blog will make you know how asynchronous testing works and why it is necessary to learn it. Jest provides a lot of ways to handle it.

Callbacks

Callbacks are a very familiar asynchronous pattern. For instance, let's say that we have fetchthedata(callback) function, which will record the data and call callback(data) as soon as it is completed. Here, we want to test what data will be returned, and the string to be returned is 'ice cream.' So, what will be the initial phase? By all means, Jest will be executed entirely once the end of the execution is reached.

// Do not do this!
test('the data will be ice cream', () => {
  function callback(data) {
    expect(data).toBe('ice cream');
  }              

  fetchData(callback);
});

 

Here, the problem in the code is that the test completes as soon as the fetch data is complete before the callback is ever called.

An alternative to this form of test exists that fixes it. Here, rather than putting the test with an empty argument in a function, we need to use a single idea that is called a zone. Jest waits until the zone callback is called before finishing the test.

test('the data will be ice cream', zone => {
  function callback(data) {
    expect(data).toBe('ice cream');
    zone();
  }
  fetchData(callback);
});

 

In the case where zone() is never called, the test fails precisely what we want to happen

Promises

If we use promises in our code, a more straightforward way to handle asynchronous tests exists. Hereafter this, you need to return a deposit from your test, and now Jest waits for that promise to resolve and complete. If, in this case where the contract is rejected, the test will fail automatically.

For instance, in fetchData, rather than using a callback, it returns a promise that we expect to resolve to the string 'ice cream'. We can test it with:

test('the data will be ice cream', () => {
  return fetchData().then(data => {
    expect(data).toBe('ice cream');
  });
});

 

Here, we need to make sure that when we return the promise and edit the return statement, our test executes before the contract gets back to fetch data resolves and then() will have the chance to complete the callback.

If we expect a promise to be not accepted, we need to use the .catch method. Ensure you add an expectation and assertions to verify that a certain number of words are called. 

test('the fetch fails with an error', () => {
  expect.assertions(1);
  return fetchData().catch(e => expect(e).toMatch('error'));
});

.resolves/.rejects

In .resolves/.rejects we can  use the .resolves matcher in the expect statement.In Jest, we will wait for that promise to resolve, and if the promise gets rejected, the test will automatically fail.

test('the data is ice cream', () => {
  return expect(fetchData()).resolves.toBe('ice cream');
});

 

We need to make sure that we return the assertion-if this return statement is omitted, our test completes before the promise gets returned from fetch data which gets resolved, and then() we will get a chance to execute the callback. If a promise gets rejected, we need to use the .rejects matcher. It will work with the .resolves matcher. Now where the promise is fulfilled, the test will automatically fail.

test('The error will be displayed after the fetch fails', () => {
  return expect(fetchData()).rejects.toMatch('error');
});

Async/Await

We can also use async and await our tests to make it more efficient. To write an async test, we need to use the async keyword with the function passed to the test. Like, the same fetch data scenario can be tested with the code below:

test('the data will be ice cream', async () => {
  const data = await fetch data();
  expect(data).toBe('ice cream');
});

test('an error gets displayed if fetch fails', async () => {
  expect.assertions(1);
  try {
    await fetchData();
  } catch (e) {
    expect(e).toMatch('error');
  }
});

 

Now, we will combine async and await with .resolves or .rejects.

test('the data will be ice cream ', async () => {
  await expect(fetchData()).resolves.toBe('ice cream');
});

test('the fetch fails with an error', async () => {
  await expect(fetchData()).rejects.toThrow('error');
});

We can mix and match them with our codebase or even within a single file. It depends upon the style that will make our test simpler.

Example

Let's implement a code that will fetch student data from an API and return the student name.

student.js

import request from './request';

export function getStudentName(studentID) {
  return request('/students/' + studentID).then(student => student.name);
}

 

Now, we expect the request.js module to return a promise in the above implementation. We chain a call to them to receive the user name.

Now imagine an implementation of request.js that goes to the network and fetches some user data:

const HTTP = require('HTTP);
export default function request(url) {
  return new Promise(resolve => {
    // Below shows an example of http request, for example to fetch
    student data from an API.
    http.get({path: url}, response => {
      let data = '';
      response.on('data', _data => (data += _data));
      response.on('end', () => resolve(data));
    });
  });

 

We will create a manual mock for our request.js module in the __mocks__ folder.

const students = {
  1: {name: 'Arav'},
  2: {name: 'Shiv'},
};

export default function request(url) {
  return new Promise((resolve, reject) => {
    const studentID = parseInt(url.substr('/students/'.length), 10);
    process.nextTick(() =>
      students[studentID]
        ? resolve(students[studentID])
        : reject({
            error: 'Student with ' + studentID + ' not found.',
          }),
    );
  });

 

Now, we will check the async functionality by using the following code.

jest.mock('../request');

import * as student from '../student';

// The assertion for  promise must be returned.
it('works with promises', () => {
  expect.assertions(1);
  return student.getStudentName(1).then(data => expect(data).toEqual('Arav'));
});

 

We can chain as many promises as we like and call expect at any time, as long as we return a Promise at the end.

.resolves

By using resolves we unwrap the value of a fulfilled promise together with other matcher. If the promise is rejected, the assertion will fail.

it('works with resolves', () => {
  expect.assertions(1);
  return expect(user.getStudentName*(2))).resolves.toEqual('Shiv');
});

 

async/await

That’s how we are going to write the code.

// async/await can be used.
it('works with async/await', async () => {
  expect.assertions(1);
  const data = await student.getStudentName(1);
  expect(data).toEqual('Arav');
});

// async/await is  used with `.resolves`.
it(' here it works with async/await and resolves', as () => {
  expect.assertions(1);
  await expect(user.getStudentName(2)).resolves.toEqual('Shiv');
});

FAQs

  1. Why is asynchronous testing done?
    When we have a code that runs asynchronously, Jest needs to know when the code it is testing has been completed before moving on to another test. To solve this issue we use asynchronous testing.
     
  2. What will happen if a promise never gets resolved?
    If a promise doesn't get resolved, our promise will never get executed and we won’t get the desired result.

Key Takeaways

This blog was all about asynchronous testing and why it's necessary. It also included the possibility of code running error-free and without redundancies with all possible outcomes. Through this blog, a clear vision of asynchronous testing and its working is portrayed. This will help you make an application run better and crack interviews. 

Please read our other blogs related to testing like Snapshot Testing, Usability Testing, etc. 

We hope you found this blog on asynchronous testing useful. Liked the blog? Then feel free to upvote and share it.

Live masterclass