Jest Testing: A Quick Guide to Effective Testing
Jest was created by Meta, mainly for testing React components. Over time, it emerged as the popular framework for testing React components, and now, many organizations are using Jest to test both front-end and back-end applications. Jest works with React apps, Babel, TypeScript, Node, Angular, and Vue-based projects. Jest got over 50 million downloads, marking it the most used framework for testing JavaScript apps.
Let’s start with unit testing first, and then we will look into further details about the framework.
Unit Testing
In unit testing, the individual component or unit of the application is tested in isolation. When we say component, it can be an individual function, method, or module. The unit test isolates that part of the code and validates its functionality. Unit testing can be considered the first testing conducted on the application. Developers usually perform it, but the QA team performs the same nowadays. The main idea of unit testing is to eliminate the bugs in small pieces of code itself. This helps to redefine the code and simplify the debugging. Unit testing can be performed manually or automated. Currently, automation is preferred as this becomes the first step in the continuous testing process.
Jest Framework
Usually, there is a misinterpretation that Jest is a library rather than a framework, which is wrong. Jest has a Command Line Interface (CLI) packed with many functionalities. Jest is built on top of Jasmine, a popular BDD tool. Jest can also be used to test every component of the application. Jest has an advantage over other tools because of its non-reliance on third-party apps. The configuration and set-up of Jest are convenient; thereby, it helps to reduce time consumption and complexity.
Now, let’s look at two main terminologies used in Jest. We can say four main features of Jest that stand out from other unit testing tools, they are:
- Zero Config: Jest aims to work out of the box, config-free, on most JavaScript projects, Meaning Jest is easy to install as a project dependency, and with minimal config, we can start creating unit test scripts.
- Snapshots: With this feature, we can avoid writing lengthy assertions for every unit; instead, we just create the snapshot of the assertions, and then every time we run, we need to validate against the snapshot only, thereby saving time.
- Isolated: Here, Jest ensures that tests run in parallel, none influence others, so every test runs on its own process. Know more about parallel testing.
- Great API: Jest has a rich API collection offering different assertion types that are well-documented and well-maintained.
Terminology in Jest
Two magical words are most commonly used in Jest: mock and spy. Let’s discuss more about each.
Mock
Mock functions are used to mock the actual implementation of a function. Consider the unit that we are going to test has a dependency on another unit, where there is an API call to that function, and based on the response, we may need to do some validations. So if that function is not ready or throws some error, our test case may give a false positive error. For such scenarios, we mock the API call so the mock API always returns an error-free value; thereby, we can validate if our function under test functions well.
const returnsTrue = jest.fn(() => false); console.log(returnsTrue()); // false;
In the above example, jest. fn() creates the mock. Here, we are mocking a return value. So whenever the user calls returnsTrue, it gives the value false.
Spy
const greet = require('./greetings'); const jest = require('jest'); describe('greet function', () => { it('should call greet function with correct parameter', () => { // Spy on greet function const spy = jest.spyOn(greet, 'name'); // Call the function with a test parameter greet('John'); // Check if the spy was called with the correct parameter expect(spy).toHaveBeenCalledWith('John'); // Restore the original implementation spy.mockRestore(); }); });
Here, jest.spyOn() is used to track calls to the greet function and its arguments. The toHaveBeenCalledWith matcher checks if the function was called with the expected parameter. Finally, mockRestore() is used to restore the original function implementation after the test.
Jest Basics
Now, let’s look into a few basic terminologies we use while writing test scripts in Jest.
describe Blocks
describe('Test Suite for MyFunction', () => { it('should do something specific', () => { // Test something here expect(...).toBe(...); }); it('should do something else, () => { // Another test expect(...).toEqual(...); }); // More tests can be added here });
So here we have grouped two validations under one single ‘describe’.
“It” or “Test” Tests
These keywords are used to start a new test case definition. The keyword ‘test’ is an alias for ‘it’. The most commonly used one is ‘it’. If you see the above example, we have started two test cases using the keywords ‘it’.
beforeAll and afterAll
If we need to make any prerequisite or to run any other function before starting the execution, we use the keyword ‘beforeAll’. So the action mentioned in ‘beforeAll’ will be executed before all test cases are managed. Similarly, if we need to perform some action, once all the test scripts are implemented, we call the method ‘afterAll’. Mostly, if we need to switch the state of a function to its ready state, we add that to ‘afterAll’ function.
Skip Tests
test.skip('should skip this test', () => { // Test implementation });
describe.skip('Test Suite to skip', () => { it('test 1', () => { // Test 1 implementation }); it('test 2', () => { // Test 2 implementation }); });
Here, we are skipping the whole block itself.
Matchers
describe('multiply function', () => { it('multiplies two numbers correctly', () => { expect(multiply(3, 4)).toEqual(12); expect(multiply(-1, 5)).toEqual(-5); expect(multiply(0, 100)).toEqual(0); }); });
In this script:
- We use ‘describe’ to group our tests for the multiply function.
- Inside ‘describe’, we define individual test cases with it.
- In each test case, we use ‘expect’ along with the ‘toEqual’ matcher to check if the result of the multiply function is as expected.
beforeEach and afterEach
describe('add function', () => { // Runs before each test beforeEach(() => { console.log('Starting a test'); }); // Test cases it('adds two positive numbers', () => { expect(add(1, 2)).toBe(3); }); it('adds two negative numbers', () => { expect(add(-1, -2)).toBe(-3); }); // Runs after each test afterEach(() => { console.log('Finished a test'); }); });
Here, ‘beforeEach‘ runs before each test. It helps set up the environment for each test. ‘afterEach‘ runs after each test. It cleans up after each test, like resetting mock states.
Jest – Setup & Execution
Before installing Jest, we need to ensure we have JavaSDK installed, NodeJS and npm, and the correct versions of browser drivers are installed. Browser drivers are required to implement Selenium WebDriver’s protocol for converting the written commands to the browser’s native API. Also, Selenium WebDriver is required if we are testing the application in parallel on our local machine.
npm install -g jest
Once Jest is installed, we can confirm its version by the command jest -version
"scripts": { "test": "jest" }
This allows you to run tests using the npm test command.
// mathFunctions.test.js const { add } = require('./mathFunctions'); describe('mathFunctions', () => { // Test for add function describe('add function', () => { it('should return the sum of two numbers', () => { expect(add(1, 2)).toBe(3); expect(add(-1, 2)).toBe(1); expect(add(-1, -2)).toBe(-3); }); it('should handle non-numeric inputs gracefully', () => { expect(add('a', 'b')).toBeNaN(); expect(add(null, undefined)).toBeNaN(); expect(add({}, [])).toBeNaN(); }); }); // Additional tests for other functions can be added here });
In this test script:
- We import the add function from mathFunctions.js.
- We use describe to create a test suite for mathFunctions.
- Inside the describe block, we write individual test cases using it.
- We use expect and matchers like toBe and toBeNaN to assert the expected outcomes.
To run the test, simply execute the npm test command in your terminal. Jest will automatically find and run files with .test.js or .spec.js suffixes.
Jest Reports
{ "pageTitle": "HTML Report", "outputPath": "testReport.html", "includeFailureMsg": true }
Also, in the scripts section in default package.json, we need to add the set CI=true.
Jest – Advantages and Limitations
Advantages | Limitations |
Zero Configuration: Jest works out of the box with minimal configuration, making it easy to set up. | Performance: While fast in most cases, Jest can be slower than other testing frameworks, especially in large projects. |
Snapshot Testing: Supports snapshot testing, which helps track changes in UI over time. | Learning Curve: The extensive range of features and matches can overwhelm beginners. |
Integrated Coverage Reports: Jest provides built-in support for test coverage reports. | Heavier Framework: It’s a more extensive package compared to more minimalistic testing libraries. |
Mocking and Spying: Easy mocking, spying, and stubbing capabilities. | Integration with Non-React Projects: While it’s excellent for React, integration with other libraries/frameworks might require additional setup. |
Watch Mode: Automatically runs tests related to changed files, making development more efficient. | Isolation: Jest runs tests in parallel in isolated environments, which can sometimes lead to unexpected issues when not properly managed. |
Great for React and JavaScript/TypeScript: Jest is optimized for testing React applications and supports JS/TS. | Verbose Mocking: Setting up complex mocks can sometimes be lengthy and convoluted. |
Rich APIs and Matchers: A wide range of APIs and matchers for various testing needs. | Different Assertion Syntax: Those familiar with other testing frameworks might find Jest’s assertion syntax slightly different. |
Community and Ecosystem: Strong community support and a rich ecosystem of plugins and extensions. | DOM Testing Limitations: Jest is not a browser testing tool; it simulates the DOM in Node.js, which might only be accurate for some use cases. |
Conclusion
Unit testing is the essential and critical testing for the application. Here, we are capturing the bug at its very early stage. For an application, we have a saying – The bug gets costly when it’s identified at a later stage. Here are the risks and impacts of late bug detection.
So, catching bugs at unit testing is always excellent and cost-effective. Jest provides a very nice interface for unit testing. Though it supports JavaScript and React applications, it still brings many limitations to the software development process.