A Complete Tutorial: Testing In Javascript with Jest
By gobrain
Sep 24th, 2024
Software testing is the process of examining a software application, especially during the development phase, to identify bugs and problems that may affect its functionality, performance, or usability.
There are many types of testing, each focusing on different parts of the software. One of the most popular testing types is unit testing. Unit testing ensures that individual components of a program function as expected. It involves isolating small units of code, typically functions or methods, and testing them independently.
When it comes to unit testing for javascript, Jest is the most popular testing framework widely used in the development community due to its ease of use, speed, and powerful features.
In this article, we will explore the basics of JavaScript unit testing using Jest, including examples and effective strategies for setting up and executing tests. Let's get started!
What is Jest?
Jest is an open-source testing framework developed by Facebook. It is designed specifically for testing JavaScript applications, particularly React applications. The framework comes bundled with various utilities that enable snapshot testing, mocking, and code coverage analysis, making it a comprehensive solution for testing JavaScript code.
Getting Started with Jest
To begin using Jest for testing your JavaScript projects, you need to set up Jest as a dev dependency in your project. You can install it using npm or yarn, depending on your preferred package manager:
npm install jest --save-dev
Once Jest is installed, you can configure it to run tests. The default configuration for Jest is simple and should work out of the box for most projects. However, for more complex projects, you can customize the configuration by creating a jest.config.js
file in the root of your project.
Writing Tests with Jest
In Jest, the describe
function is a fundamental building block used for organizing and grouping related test cases. In it, Jest allows you to write test cases using the test()
or it()
functions, which take a description of the test and a function containing the actual test code.
The test function typically contains the logic to be tested and some assertions to verify the expected behavior.
Let’s look at a simple example to illustrate how to write a test using Jest:
// sum.js
function sum(a, b) {
return a + b;
}
module.exports = sum;
const sum = require("./sum");
describe("Test sum function", () => {
test("adds 1 + 2 to equal 3", () => {
expect(sum(1, 2)).toBe(3);
});
});
In this example, we have a simple sum
function that adds two numbers. We then create a test file named sum.test.js
, and within this file, we import the sum
function and write a test case using the test()
function. The expect()
function is used to define the expected outcome, and toBe()
is the matcher to check if the result matches the expected value.
How To Run Jest Tests
After writing the test cases, we need to execute them using Jest. Jest provides a convenient command-line interface to run tests. By default, Jest looks for test files in the __tests__
folder or any file with a .test.js
or .spec.js
extension.
To execute the tests, you can run the following command:
npx jest
Jest will automatically find and run all the test files in the project and display the test results in the console.
Assertions
In Jest, assertions are statements that check whether the actual result of an operation matches the expected result. Jest provides various assertion functions to compare and validate values. Some commonly used ones include:
expect(value)
– The starting point for any assertion..toBe(expected)
– Tests exact equality (for primitive values like numbers, strings, etc.)..toEqual(expected)
– Tests deep equality (for objects and arrays)..toBeTruthy()
– Tests if a value is truthy..toBeFalsy()
– Tests if a value is falsy..not.toBe(expected)
– Tests for inequality.
For example, toContain
assertion can be used to check whether an array contain in a specific element as shown below:
test('checks if an array contains a specific element', () => {
const myArray = [1, 2, 3];
expect(myArray).toContain(2);
});
Or, the toThrow
assertions is used to check if a function throws an exception when called.
function divide(a, b) {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
test('throws an error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
These a few assertion examples in Jest, you can visit the Jest documentation for more assertion function.
Mocking
Mocking is a technique used in testing to create and control specific behavior of objects or functions that a unit being tested interacts with. In Jest, mocking is a powerful feature that allows you to replace real dependencies with “mock” implementations.
Jest API for mocking includes:
jest.fn()
: Creates a mock function.jest.mock('module-name')
: Automatically mocks a module or dependency.jest.spyOn(object, methodName)
: Creates a spy on an existing method of an object, allowing you to track calls and return values.
Now, suppose you have a simple function that fetches data from an API and processes it:
const axios = require("axios");
async function fetchData() {
try {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts/1"
);
return response.data;
} catch (error) {
throw new Error("Failed to fetch data");
}
}
module.exports = { fetchData };
```js
In this example, we’re using the (JSONPlaceholder Fake API) [https://jsonplaceholder.typicode.com](https://jsonplaceholder.typicode.com/) to fetch a sample post. The `fetchData` function makes an HTTP GET request using `axios` and returns the data.
You can write a test for this function with mocking as follows:
```js
// .fetchData.test.js
const { fetchData } = require("./fetchData");
const axios = require("axios");
jest.mock("axios"); // Automatically mocks axios
test("fetchData function should return data", async () => {
const mockData = { title: "Mocked Title", body: "Mocked Body" };
axios.get.mockResolvedValue({ data: mockData });
const data = await fetchData();
expect(data).toEqual(mockData);
});
test("fetchData function should throw an error on API error", async () => {
const errorMessage = "Network Error";
axios.get.mockRejectedValue(new Error(errorMessage));
await expect(fetchData()).rejects.toThrow("Failed to fetch data");
});
In the test file, we use jest.mock
to automatically mock axios
, and then we use axios.get.mockResolvedValue
to simulate a successful API response with the mockData
. We also test the scenario where the API call fails by using axios.get.mockRejectedValue
.
Asynchronous Testing
Asynchronous testing in Jest allows you to write and run tests that involve asynchronous operations, such as network requests, timers, or Promises. For testing asynchronous code (e.g., async/await, Promises, callbacks), use Jest’s async
and await
, or the done
callback for callbacks.
- Using
done
callback: The simplest way to handle asynchronous testing is by using thedone
callback. In this approach, you calldone
when your asynchronous code is complete, and Jest will wait until thedone
callback is called or a timeout occurs.
test('example asynchronous test', (done) => {
// Assume some asynchronous code or operation
setTimeout(() => {
// Your test assertions go here
expect(true).toBe(true);
// Call the done callback to signal the test is complete
done();
}, 1000); // This test will wait for at most 1000ms for the done callback
});
- Using
async/await
Jest also supports using async/await
syntax, which makes your asynchronous tests look more like synchronous code. When you return a Promise from the test function, Jest will automatically wait for the Promise to resolve.
test('example asynchronous test with async/await', async () => {
// Assume some asynchronous code or operation
await someAsyncFunction();
// Your test assertions go here
expect(true).toBe(true);
});
- Using
resolves
andrejects
matchers
If you’re testing functions that return Promises, Jest provides special matchers to handle Promise resolution and rejection.
// For resolving Promises
test('example asynchronous test with resolves', () => {
return expect(someAsyncFunction()).resolves.toBe('expectedValue');
});
// For rejecting Promises
test('example asynchronous test with rejects', () => {
return expect(someAsyncFunction()).rejects.toThrow('error message');
});
Conclusion
Testing with Jest is an essential part of modern JavaScript development. It provides a simple yet powerful framework to write and execute tests.
Remember that writing effective tests requires practice and attention to detail. As your project grows, invest time in maintaining and expanding your test suite to cover new features and potential edge cases.
Thank you for reading.