Jest - Performance and Advanced Topics



In this chapter, we'll cover advanced Jest features to help you write faster and more efficient tests. The topics we will cover include:

Jest - Performance Optimization

Optimizing Jest's performance is important for larger projects or when dealing with many test cases. Here are several strategies you can use to speed up Jest tests:

Run Tests in Parallel

By default, Jest runs tests in parallel, which speeds up test execution. It divides tests into multiple workers(processes) based on the number of CPU cores, helping to reduce test run time.

You can adjust the number of workers in the Jest configuration file(jest.config.js) using the maxWorkers option.

module.exports = {
    maxWorkers: "50%", // Use 50% of available CPU cores
};

Avoid Large Test Suites

For large test suites, split them into smaller chunks by organizing tests into separate files or using the testPathPattern option to run specific tests.

jest --testPathPattern="specificTestFile"

Avoiding Slow Setup/Teardown

To optimize slow setup or teardown (like database connections), use beforeAll and afterAll to avoid repeating operations in beforeEach and afterEach.

Use jest.runAllTimers() and jest.clearAllMocks()

To improve test performance, clear unused mocks and timers to prevent unnecessary code from running. For example:

beforeEach(() => {
    jest.clearAllMocks();  // Clears any mocks that are no longer needed
    jest.runAllTimers();   // Runs all pending timers immediately
});

Use --silent Flag for CI Environments

In a continuous integration (CI) environment, use the --silent flag to prevent unnecessary output in the console, which helps reduce the load during test execution.

jest --silent

Optimize Test Setup and Teardown

Avoid heavy operations in beforeAll or afterAll. Keep setup and teardown minimal for faster execution. If your tests interact with a database, consider mocking the database to improve performance.

Jest - Custom Matchers

Custom matchers in Jest allow you to create your own expectations, making your tests clearer and more suited to your needs. They are especially useful for testing specific behaviors or custom data structures.

Creating a Custom Matcher

To create a custom matcher, use expect.extend(). Here's how you can define one that checks if a number is divisible by another number:

expect.extend({
    toBeDivisibleBy(received, argument) {
        const pass = received % argument === 0;
        if (pass) {
            return {
                message: () => `expected ${received} not to be divisible by ${argument}`,
                pass: true,
            };
        } else {
            return {
                message: () => `expected ${received} to be divisible by ${argument}`,
                pass: false,
            };
        }
    },
});

Using the Custom Matcher

Once you've defined a custom matcher, you can use it in your tests like this:

test('is divisible by 3', () => {
    expect(6).toBeDivisibleBy(3); // passes
    expect(7).toBeDivisibleBy(3); // fails
});

Async Custom Matchers

You can also create asynchronous custom matchers for tests involving promises or async operations. Here's an example of an async matcher that checks if data fetched from a URL contains a specific value:

expect.extend({
    async toFetchData(received) {
        const response = await fetch(received);
        const data = await response.json();
        return {
            message: () => `expected ${received} to fetch data`,
            pass: data.someProperty === 'expectedValue',
        };
    },
});

Jest - Timer Mocks

Timers like setTimeout and setInterval are common in JavaScript. Jest provides tools to mock and control them in tests, allowing you to test time-dependent functions without waiting for actual time to pass.

Mocking Timers Using Jest

To mock timers in Jest, use jest.useFakeTimers() at the beginning of your tests. This replaces the default timers with mock timers that you can control.

beforeEach(() => {
    jest.useFakeTimers();  // Enable fake timers
});

afterEach(() => {
    jest.useRealTimers();  // Restore real timers after each test
});

Advancing Timers

Once timers are mocked, you can manually advance them using jest.advanceTimersByTime() or jest.runAllTimers()

  • jest.advanceTimersByTime(ms): Advances the timer by the specified number of milliseconds.
test('calls the callback after timeout', () => {
    const callback = jest.fn();
    setTimeout(callback, 1000);

    jest.advanceTimersByTime(1000); // Fast-forward 1000ms
    expect(callback).toHaveBeenCalled();
});
    
  • jest.runAllTimers(): Runs all pending timers until completion.
  • test('executes all timers', () => {
        const callback = jest.fn();
        setTimeout(callback, 500);
        setTimeout(callback, 1000);
    
        jest.runAllTimers(); // Executes both timeouts
        expect(callback).toHaveBeenCalledTimes(2);
    });
        

    Clearing Mock Timers

    To prevent tests from affecting each other, clear mock timers using jest.clearAllTimers() or restore the original timers with jest.useRealTimers() between tests.

    afterEach(() => {
        jest.clearAllTimers();
    });
    

    Jest - DOM Testing

    Jest works well for DOM testing when used with libraries like React Testing Library or Enzyme. These libraries use jsdom to simulate the DOM environment in Node.js.

    Setting Up DOM Testing with Jest

    Jest uses jsdom by default, so no extra setup is needed. To interact with the DOM, install React Testing Library or Enzyme.

    To install React Testing Library:

    npm install @testing-library/react @testing-library/jest-dom
    

    Writing DOM Tests

    Once your setup is ready, you can write tests for your components. Here's an example of how to test a button click in a React component.

    import { render, screen, fireEvent } from '@testing-library/react';
    import MyComponent from './MyComponent';
    
    test('button click changes text', () => {
        render(<MyComponent />);
        const button = screen.getByRole('button');
        fireEvent.click(button);
        const text = screen.getByText(/button clicked/i);
        expect(text).toBeInTheDocument();
    });
    

    Assertions with Jest DOM

    jest-dom adds extra matchers to Jest for testing DOM elements. Some useful matchers are:

    • .toBeInTheDocument(): Checks if an element is in the DOM.
    • .toHaveTextContent(): Verifies if an element contains the given text.
    • .toBeVisible(): Ensures the element is visible on the screen.

    Example

    import '@testing-library/jest-dom'; // Provides extra matchers
    
    test('button displays correct text', () => {
        render(<MyComponent />);
        const button = screen.getByRole('button');
        expect(button).toHaveTextContent('Click me');
        expect(button).toBeVisible();
    });
    

    Cleaning Up After Tests

    Jest automatically cleans up the DOM after each test. However, you can also manually clear the DOM if needed using the cleanup() function from React Testing Library.

    Example

    import { cleanup } from '@testing-library/react';
    
    afterEach(() => {
        jest.clearAllMocks();
        cleanup();
    });
    

    This clears the DOM after each test, preventing interference between tests.

    Advertisements