JavaScript > Testing and Debugging > Unit Testing > Writing test cases

Asynchronous Function Unit Tests with Jest

This example demonstrates how to write unit tests for an asynchronous function using Jest. It covers testing functions that use Promises and async/await syntax, ensuring that asynchronous operations are handled correctly.

The Asynchronous Function

This function fetchData uses async/await to fetch data from a URL using the fetch API. It handles potential errors, such as network issues or HTTP errors, and returns the parsed JSON data.

async function fetchData(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Fetching data failed:', error);
    throw error;
  }
}

Jest Test Suite for Asynchronous Function

This Jest test suite tests the fetchData function. It uses jest.fn() to mock the fetch API, allowing you to control the responses and simulate different scenarios. The first test case verifies that the function fetches data successfully. The second test case verifies that the function handles HTTP errors correctly. The third test case verifies that the function handles network errors correctly. The async/await syntax is used to handle the asynchronous nature of the function. The expect(...).rejects.toThrow() syntax is used to assert that the function throws an error when an error occurs.

describe('fetchData', () => {
  it('should fetch data successfully', async () => {
    global.fetch = jest.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve({ id: 1, name: 'Test' }),
    });

    const data = await fetchData('https://example.com/data');
    expect(data).toEqual({ id: 1, name: 'Test' });
    expect(fetch).toHaveBeenCalledWith('https://example.com/data');
  });

  it('should handle errors when fetching data', async () => {
    global.fetch = jest.fn().mockResolvedValue({
      ok: false,
      status: 404,
    });

    await expect(fetchData('https://example.com/data')).rejects.toThrow('HTTP error! status: 404');
    expect(fetch).toHaveBeenCalledWith('https://example.com/data');
  });

  it('should handle network errors', async () => {
    global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));

    await expect(fetchData('https://example.com/data')).rejects.toThrow('Network error');
    expect(fetch).toHaveBeenCalledWith('https://example.com/data');
  });
});

Concepts Behind the Snippet

This snippet demonstrates how to test asynchronous functions using Jest. It uses mocking to simulate the behavior of external dependencies, such as the fetch API. It also demonstrates how to use async/await to handle asynchronous operations in test cases and how to assert that a function throws an error using rejects.toThrow().

Real-Life Use Case Section

Testing asynchronous functions is essential when dealing with API calls, database queries, or any other operation that involves waiting for a response. For example, in a web application that fetches data from a remote server, you need to ensure that the application handles network errors, server errors, and unexpected responses gracefully. Unit tests help you verify that these scenarios are handled correctly.

Best Practices

  • Mock External Dependencies: Use mocking to isolate the function being tested and control the behavior of external dependencies.
  • Use async/await: Use async/await to simplify asynchronous test cases and make them easier to read.
  • Test Error Handling: Always test error handling scenarios to ensure that the function handles errors correctly.
  • Verify Function Calls: Use expect(function).toHaveBeenCalledWith(...) to verify that the function was called with the correct arguments.

Interview Tip

When discussing asynchronous testing in an interview, demonstrate your understanding of Promises, async/await, and mocking. Be prepared to explain how you would test different scenarios, such as successful responses, error responses, and network errors. Show that you understand the importance of isolating the function being tested from external dependencies.

When to Use Them

Use these techniques whenever you are testing asynchronous functions, such as functions that make API calls, interact with databases, or perform other time-consuming operations.

Memory Footprint

The memory footprint of these tests is generally small. Mocking frameworks might introduce a slight overhead, but the overall impact is usually negligible.

Alternatives

Alternatives to mocking the fetch API include using a library like nock to intercept HTTP requests and return mock responses. You can also use real HTTP requests in integration tests to test the entire system.

Pros

  • Isolate the Function: Mocking allows you to isolate the function being tested and control the behavior of external dependencies.
  • Test Error Handling: You can easily simulate error scenarios and verify that the function handles them correctly.
  • Improve Test Speed: Mocking can significantly improve the speed of tests by avoiding real network requests or database queries.

Cons

  • Requires Setup: Mocking requires setting up mock objects and defining their behavior.
  • Potential for Incorrect Mocks: If the mocks are not set up correctly, the tests may not accurately reflect the behavior of the real system.
  • Maintenance Overhead: Mocks need to be maintained and updated as the code changes.

FAQ

  • What is mocking, and why is it used in unit testing?

    Mocking is the process of creating simulated objects or functions that mimic the behavior of real dependencies. It is used in unit testing to isolate the code being tested and control the environment, allowing you to test different scenarios and error conditions without relying on external systems.
  • Why use global.fetch to mock the fetch API?

    In a Node.js environment, the fetch API is not globally available by default. By assigning the mocked function to global.fetch, you make it available to the fetchData function during the test.
  • How do I handle timeouts in asynchronous tests?

    Jest provides a timeout option that you can configure for individual tests or for the entire test suite. You can set the timeout value using the timeout option in the it block or by setting the jest.setTimeout function.