Jest unit testing: a little hide and seek with the code!

Hi, fellow coders! In this golden October, the National Day overlaps with the Mid-Autumn Festival, and we have a special task – to find the mysterious “Mid-Autumn Cake” of the code through Jest unit testing! It’s a simple and fun adventure, as easy as looking for the moon at a Mid-Autumn Festival party. Let us dig out the treasures of code together and send you double blessings on National Day and Mid-Autumn Festival!

Jest

What is Jest?

Jest is a popular JavaScript testing framework focused on simplifying and improving the testing process for your code. It is developed and maintained by Facebook and has the following features:

1. Ease of use: Jest provides a simple and powerful testing framework that makes writing and running tests very easy.

2. Automation: It automatically discovers and runs tests without complex configuration, and you can start testing your code immediately.

3. Assertion library: Jest integrates a powerful assertion library to verify the expected behavior of the code and help you catch potential problems.

4. Simulation and mock functions: Jest supports simulated testing environments, making it easy to simulate functions and modules, thereby improving testing efficiency.

5. Snapshot tests: Jest allows you to easily create and maintain snapshot tests to ensure that the rendering and structure of UI components do not change unexpectedly.

6. Concurrent testing: It can run tests in parallel, improving the speed of testing, and is especially suitable for large code bases.

7. Rich plug-in ecosystem: There are many plug-ins and extensions in Jest’s ecosystem to meet various testing needs.

Why choose Jest?

Jest is popular among the development community because it has everything you need for front-end and back-end JavaScript testing and is very easy to get started with. It is not only used for unit testing but also for integration testing and end-to-end testing. Jest’s automation features and powerful functionality make testing easier and more efficient, helping to improve code quality and reduce potential problems. Whether you are a front-end or back-end developer, Jest is a testing tool worth considering.

Introduction

Jest is a testing framework based on Jasmine and Mocha. It provides an easy-to-use API for writing concise and maintainable test cases. Jest also integrates assertion libraries, mock function tools, and code coverage reporting.

Installation

First, make sure you have initialized npm in the project directory. Then, install Jest using the following command:

npm install --save-dev jest

After installation is complete, you can add a command in the “scripts” section of package.json to run tests more conveniently:

"scripts": {<!-- -->
  "test": "jest"
}

Next we can start happily writing test cases.

Write test cases

First create a file called sum.js that contains the following functions:

// sum.js
function sum(a, b) {<!-- -->
  return a + b;
}

module.exports = sum;

Now, create a file called sum.test.js to write test cases for the sum function:

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

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

In this test case, we use the test global function to define a test. Among them, the first parameter is the description of the test, and the second parameter is a function in which we write the test logic. Use the expect function to assert our test results.

Run the test

Now you can run the test. Run the following command in the terminal:

npm test

Assertion

Jest provides rich assertion methods for verifying expected results. Here are some commonly used assertion methods:

expect(value).toBe(expected): Check whether value is equal to expected.

expect(value).toEqual(expected): Check whether value is equal to expected.

expect(value).toBeNull(): Check whether value is null.

expect(value).toBeDefined(): Check whether value is defined.

expect(value).toBeTruthy(): Check whether value is true.

expect(value).toBeFalsy(): Check whether value is false.

expect(value).not.toBe(expected): Check whether value is not equal to expected.

Asynchronous testing

When dealing with asynchronous logic, Jest provides multiple ways to write and handle asynchronous tests.
There are two commonly used methods:

Use async and await keywords:

test('async test', async () => {<!-- -->
  const data = await fetchData();
  expect(data).toEqual(expectedData);
});

Use the done parameter:

test('callback test', (done) => {<!-- -->
  fetchData((data) => {<!-- -->
    expect(data).toEqual(expectedData);
    done();
  });
});

Mocking

In testing, we often need to simulate the behavior of a function or module. Jest provides built-in mock function tools to achieve this functionality.

Here’s an example using Jest’s mock function:

function fetchData(callback) {<!-- -->
  // Assume this is an asynchronous operation
  setTimeout(() => {<!-- -->
    callback('Hello Jest!');
  }, 1000);
}

test('mocking test', () => {<!-- -->
  const mockCallback = jest.fn();
  fetchData(mockCallback);

  expect(mockCallback).toHaveBeenCalledTimes(1);
  expect(mockCallback).toHaveBeenCalledWith('Hello Jest!');
});

In the above example, we create a mock function mockCallback using jest.fn() and then pass it as a callback function to the fetchData function. By using jest.fn(), we can track the number of calls to this mock function and the parameters passed in for assertions.

Code Coverage

Code coverage is a measure of test coverage. Jest provides built-in code coverage tools that can help you analyze test coverage.

Code coverage reports can be generated by adding the following configuration in package.json:

"scripts": {<!-- -->
  "test": "jest --coverage"
}

After running the npm test command, Jest will generate a code coverage report showing your test coverage.

Advanced configuration

Jest provides a wealth of configuration options to meet the needs of the project. You can create a jest.config.js file in the project root directory to configure Jest.

Here is a simple configuration example:

// jest.config.js
module.exports = {<!-- -->
  verbose: true,
  testEnvironment: 'node',
  coverageDirectory: 'coverage',
  collectCoverageFrom: ['src/**/*.js'],
};

Exception test

In the test code, we need to ensure that exceptions are handled correctly. Jest provides multiple assertion methods to handle exceptions.

function divide(a, b) {<!-- -->
  if (b === 0) {<!-- -->
    throw new Error('Divide by zero');
  }
  return a / b;
}

test('divide should throw an error when dividing by zero', () => {<!-- -->
  expect(() => {<!-- -->
    divide(10, 0);
  }).toThrow('Divide by zero');
});

In the above example, we use the toThrow assertion method to verify that the code throws the expected error.

Testing asynchronous code for errors

When testing asynchronous code, you must ensure that errors in asynchronous operations are caught. Jest provides several ways to handle this situation.

async function fetchData() {<!-- -->
  return new Promise((resolve, reject) => {<!-- -->
    setTimeout(() => {<!-- -->
      reject('Fetch error');
    }, 1000);
  });
}

test('fetchData should throw an error', async () => {<!-- -->
  expect.assertions(1);
  try {<!-- -->
    await fetchData();
  } catch (error) {<!-- -->
    expect(error).toEqual('Fetch error');
  }
});

In the above example, we use expect.assertions to ensure that at least one assertion is executed. Then use a try-catch block to catch the error in the fetchData function and use an assertion to verify the error value.

The number of method calls of the test object

Sometimes we need to ensure that an object’s method is called correctly a specified number of times. Jest provides methods for checking the number of calls to a mocked function.

class Counter {<!-- -->
  constructor() {<!-- -->
    this.count = 0;
  }

  increment() {<!-- -->
    this.count + + ;
  }
}

test('Counter increment method should be called twice', () => {<!-- -->
  const counter = new Counter();
  counter.increment();
  counter.increment();
  const incrementMock = jest.spyOn(counter, 'increment');

  expect(incrementMock).toHaveBeenCalledTimes(2);
});

In the above example, we use jest.spyOn to monitor the increment method of the Counter class, and then verify that the method was called correctly twice by calling the increment method twice and using the toHaveBeenCalledTimes assertion method.

Test component interaction

When testing React components, we often need to simulate user interaction and verify the behavior of the component. Jest provides some methods and tools to help test React components.

import {<!-- --> render, fireEvent } from '@testing-library/react';
import Button from './Button';

test('Button click should trigger callback', () => {<!-- -->
  const handleClick = jest.fn();
  const {<!-- --> getByText } = render(<Button onClick={<!-- -->handleClick}>Click me</Button>);
  const button = getByText('Click me');
  fireEvent.click(button);

  expect(handleClick).toHaveBeenCalled();
});

In the above example, we use the render function and fireEvent tool from the @testing-library/react library to render and test the component. Then use jest.fn to create a mock function to monitor the callback function and trigger the callback by simulating a button click, and use the toHaveBeenCalled assertion method to verify whether the callback function is called.

Snapshot test

Snapshot testing is a testing method used to capture the initial rendering and state of a component or data structure. Jest’s toMatchSnapshot method can be used to create and compare snapshots.

import renderer from 'react-test-renderer';
import MyComponent from './MyComponent';

test('MyComponent snapshot', () => {<!-- -->
  const tree = renderer.create(<MyComponent />).toJSON();
  expect(tree).toMatchSnapshot();
});

The first time you run a test, Jest will create a snapshot file and then compare the snapshot to the new rendering results on subsequent runs. This helps detect if a component has changed unexpectedly.

Parametric testing

Sometimes we need to test a set of similar inputs and can use parameterized tests to reduce code duplication.

const testData = [
  {<!-- --> input: 2, expected: 4 },
  {<!-- --> input: 3, expected: 9 },
  {<!-- --> input: 4, expected: 16 },
];

test.each(testData)('square(%i) should return %i', (input, expected) => {<!-- -->
  expect(square(input)).toBe(expected);
});

In the above example, we use the test.each method to define a parameterized test that will run the same test code multiple times based on different input values, thus avoiding duplicate test cases.

Custom matcher

Jest allows you to create custom matchers to make it easier to write application-specific assertions.

expect.extend({<!-- -->
  toBeValidEmail(received) {<!-- -->
    const regex = /^[A-Z0-9._% + -] + @[A-Z0-9.-] + .[A-Z]{<!-- -->2,}$/i;
    const pass = regex.test(received);
    if (pass) {<!-- -->
      return {<!-- -->
        message: () => `expected ${<!-- -->received} not to be a valid email`,
        pass: true,
      };
    } else {<!-- -->
      return {<!-- -->
        message: () => `expected ${<!-- -->received} to be a valid email`,
        pass: false,
      };
    }
  },
});

test('email validation', () => {<!-- -->
  expect('[email protected]').toBeValidEmail();
  expect('invalid-email').not.toBeValidEmail();
});

In the above example, we created a custom matcher toBeValidEmail that verifies that a string is a valid email address. This allows us to use custom assertions to verify specific behavior of the application.

Use beforeEach and afterEach

The beforeEach and afterEach functions allow you to perform specific actions before and after each test case, such as setting up and cleaning up the test environment.

let counter = 0;

beforeEach(() => {<!-- -->
  counter + + ;
});

afterEach(() => {<!-- -->
  counter = 0;
});

test('increment counter', () => {<!-- -->
  expect(counter).toBe(1);
});

test('reset counter', () => {<!-- -->
  expect(counter).toBe(1);
  counter = 5;
  expect(counter).toBe(5);
});

In the above example, beforeEach is used to increment the counter before each test case, while afterEach is used to reset the counter to 0 after each test case to ensure isolation of tests.

Test component life cycle methods

If you use React or another library that supports lifecycle methods, you can use jest and enzyme (or other libraries) to test your component’s lifecycle methods.

import React from 'react';
import {<!-- --> mount } from 'enzyme';
import MyComponent from './MyComponent';

test('componentDidMount is called', () => {<!-- -->
  const componentDidMountSpy = jest.spyOn(MyComponent.prototype, 'componentDidMount');
  const wrapper = mount(<MyComponent />);
  expect(componentDidMountSpy).toHaveBeenCalled();
  componentDidMountSpy.mockRestore();
});

In the above example, we use enzyme to mount a React component and use jest.spyOn to monitor calls to the componentDidMount lifecycle method.

Simulation time

Sometimes you need to test time-related operations, such as setTimeout or setInterval. Jest provides a way to simulate time.

jest.useFakeTimers();

test('setTimeout test', () => {<!-- -->
  const callback = jest.fn();
  setTimeout(callback, 1000);

  jest.advanceTimersByTime(1000);

  expect(callback).toHaveBeenCalled();
});

In this example, we use jest.useFakeTimers() to simulate time and jest.advanceTimersByTime to advance time to trigger the setTimeout callback function.

Finally: The complete software testing video tutorial below has been compiled and uploaded. Friends who need it can get it by themselves [Guaranteed 100% Free]

Software testing interview document

We must study to find a high-paying job. The following interview questions are from the latest interview materials from first-tier Internet companies such as Alibaba, Tencent, Byte, etc., and some Byte bosses have given authoritative answers. After finishing this set I believe everyone can find a satisfactory job based on the interview information.