
If you develop interfaces in React or other frontend frameworks , you'll sooner or later realize that relying solely on "it works on my machine" is playing with fire. A small change to a component, a quick refactoring, or an updated dependency can break parts of the application without you even noticing... unless you have a good unit testing system set up with Jest.
The modern JavaScript ecosystem has automated testing in its DNA. Tools like Jest make writing tests for components , functions, and hooks manageable on a daily basis, even if you're not a testing enthusiast. The key is having a comfortable setup, a solid understanding of how tests are written, and knowing how to interpret the results and coverage to identify areas of your code that are going untested.
Why do unit testing in frontend projects?
Unit tests are small tests that validate specific pieces of your code (functions, components, hooks, utilities, etc.). They are especially useful in frontend development because the interface changes frequently, there's state logic, user events, asynchronous calls, and so on. Without a safety net, every change is a gamble.
Among the clearest benefits of unit testing is the early detection of errors . Instead of discovering bugs when the user is already in production, you detect them as soon as you save changes and run the test suite, which helps you better understand the bug's lifecycle . This saves the team time, money, and a lot of headaches.
Another powerful point is that tests end up functioning as living documentation . Seeing how a test for a component or function is written makes it clear how it's expected to be used, what inputs it accepts, and what results it should return. In large projects, this is invaluable for new team members.
In the context of JavaScript and React, writing tests also helps to better modularize the code . To be able to test a piece separately, it needs to be well isolated, with clear dependencies and defined responsibilities, which translates into a more maintainable frontend in the medium and long term.
What is Jest and why is it used so much in frontend development?
Jest is a JavaScript testing framework originally developed by Facebook , designed to work wonderfully with React, but perfectly valid for any client-side or server-side JavaScript or TypeScript project.
One of its greatest strengths is its "zero configuration" philosophy . In many projects, simply installing it and adding a script to the package.json file is enough to start running tests without getting bogged down in complex configuration files. This makes it especially attractive in frontend environments where many tools are already in use.
Jest integrates key features for frontend projects as standard : fast test execution, watch mode that reruns tests when changes are detected, very convenient support for asynchronous code, mocks and spies to simulate dependencies, and generation of code coverage reports without depending on additional external tools.
For frontend projects using React, Jest is almost always combined with the React Testing Library , a library that makes it easier to test components through their behavior and rendering, rather than being overly tied to the internal implementation. Together, these two tools cover virtually all common frontend testing needs.
Installing Jest and React Testing Library in a frontend project
The first step to getting started with Jest is to add it as a development dependency to your project. If you're using npm, the typical command would be:
npm install --save-dev jest
If you prefer to work with Yarn , you can install it with:
yarn add --dev jest
In React-based projects, it is very common to also install the React Testing Library , which consists of several packages: the base @testing-library/react for components, @testing-library/jest-dom for additional matchers on the DOM, and often @testing-library/user-event to simulate complex user interactions.
A typical React installation might look something like this :
npm install –save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event
With these dependencies in place, your environment is ready to write unit tests focused on components , events, and user-visible results, always relying on Jest as the main test execution engine.
Basic Jest configuration in package.json
Once Jest is installed, you need to tell the project how to run tests . The usual way is to add a script to the package.json file to have a simple command from the terminal.
A minimal example of a configuration could be :
{ «scripts»: { «test»: «jest» } }
With this script you can run all the project tests by simply launching :
npm testing
or, if you use yarn, with :
yarn test
Jest automatically detects test files based on their names . By default, it will look for files with the suffixes .test.js or .spec.js in your folder tree, so you usually don't need to specify paths manually as long as you follow these conventions.
File convention: .test.js extension and test structure
To ensure Jest recognizes your tests without extra configuration, it's highly recommended to use the .test.js extension (or .test.ts if you're working with TypeScript). For example, if you have a Button.jsx component, a common name for its test would be Button.test.js, either in the same directory or in a separate tests folder.
This convention has two clear advantages :
- On one hand, Jest automatically locates the files to be executed.
- On the other hand, anyone new to the project immediately knows which files contain production code and which contain tests.
Tests are defined using the `test` function or its alias `it` , where the first argument is a text description of what you are testing and the second is a function that executes the test logic. Assertions are used within this function, through `expect` and its various matchers.
In React components the structure is similar , only instead of testing a pure function, you render the component with the React Testing Library, search for elements on the screen (by text, role, labels, etc.) and check that they are displayed or react as you expect when simulating user interactions.
Write your first unit test with Jest
To bring all this down to earth, imagine a simple function that adds two values . In a file called sum.js, you define function sum(a, b) { return a + b; } and export it. Then, in sum.test.js, you import that function and define a test with a clear description of what should happen.
The test body is limited to executing the function and validating the result : you call sum(1, 2) and use expect to indicate that the value must be exactly 3. If the function stops returning that result (due to a bug or an unforeseen modification), Jest will mark the test as failed.
This type of test, however simple it may seem, is the foundation of unit testing . Each function or logical unit has one or more tests that describe what it should do in different scenarios, so any deviation from the expected behavior is immediately apparent upon running the test suite.
In frontend components the approach is just as straightforward : you render the component, check what is displayed, simulate events such as clicks or writing to inputs, and validate that the state and the resulting DOM match what defines the functional design of the application.
As the project grows, you will add more tests to cover edge cases , atypical inputs, errors, and less obvious situations, reinforcing the reliability of the application and preventing regressions when you introduce new features.
Jest Matchers: different ways to check results
The core of assertions in Jest is the `expect` function , which chains with different matchers to specify the requirements of the received value. Depending on what you're testing, you'll want to use one or another. These are the most practical matchers:
- toBe. It checks for strict equality, which in JavaScript means the same value and the same type, very useful for numbers, strings, or booleans where you expect an exact match. If you expect 3, you don't want "3" to arrive.
- toEqualUseful when working with objects or arrays. This matcher compares the structure and content of objects, so you can verify that a function returns an object with the correct properties and values, even if the internal reference is not the same.
- not. If at any point you need to ensure that something does NOT happen, you can use the negation `not`. For example, `expect(value).not.toBe(0)` makes it clear that a number should not be zero, or `expect(array).not.toEqual([])` indicates that you do not expect an empty array.
In addition to these basics, Jest offers many other matchers : to check that a function throws an error, that an array contains a specific element, that a string matches a regular expression, or, with jest-dom in the case of React, that a DOM element is visible, disabled, has certain text, etc.
Asynchronous testing in Jest: promises, async/await, and callbacks
The modern frontend is full of asynchronous operations : HTTP requests, timers, user interactions that trigger state updates, and so on. That's why Jest integrates several ways to work comfortably with asynchronous tests.
The cleanest and most common way to test asynchronous logic is to use async/await . You declare your test function as asynchronous, wait for the promise you're testing to resolve, and then make the usual assertions with expect on the received result.
For example, you could have a fetchData function that returns a promise and write an asynchronous test that calls fetchData, waits for it to resolve, and verifies that the returned data matches what you expect, whether it's a specific text or an object with a certain structure.
Jest also directly supports promises without async/await , returning the promise itself from the test so the framework knows when the operation has finished. Additionally, in older or very specific cases, it allows the use of callbacks with a `done` parameter to indicate the end of the test.
In the React Testing Library, asynchronous tests often combine waits with findBy or waitFor , which allow you to wait for the DOM to update after a request or a state change before making the relevant assertions.
Mocking in Jest: simulating modules, functions, and dependencies
A basic principle of unit testing is to isolate the unit under test . This means that if a function or component depends on external services (APIs, third-party libraries, heavyweight modules, etc.), you want to simulate those behaviors rather than actually running them during the test.
Jest facilitates this isolation through mocks . With jest.fn you can create mock functions that record how many times they are called, with what arguments, or what value they should return. This is very useful for testing internal interactions without having to touch the actual code of those services.
When you need to go a step further, jest.mock lets you replace entire modules . You can specify that, when importing a particular file, Jest should use a mock implementation that returns controlled values, avoiding, for example, sending real HTTP requests every time the test suite runs.
In React components, mocks are frequently used to simulate custom hooks , data services, or modules that handle local storage, analytics, etc., keeping testing focused on the component's behavior rather than that of its external dependencies.
When used correctly, mocking greatly speeds up test execution and allows for easy reproduction of error scenarios, strange server responses, or atypical states that would be difficult to achieve by interacting with real services.
Organizing and grouping tests with blocks describes
As your test suite grows, you need to maintain a minimum level of organization to avoid getting lost among hundreds of tests spread across multiple files. Jest offers blocks as a natural way to group related tests.
With `describe` you can enclose several tests under the same context , for example, "arithmetic operations" or "Header component behavior". Within the block, each test describes a specific case, but the set reads like a coherent narrative about that part of the code.
This organization is helpful both for reading and debugging . When something goes wrong, it's easier to locate the test suite and understand which part of the system it affects, without having to scan the entire project.
Furthermore, it combines very well with Jest lifecycle hooks , such as beforeEach or afterEach, allowing you to prepare data or clean shared states for all tests within the same block, avoiding duplicating initialization logic in each individual test.
In complex frontend projects it is common to have a describe file for each component , subdivided if necessary into internal descriptions for different modes, props or interaction flows, which turns the test file into a fairly clear map of everything that is expected of that component.
Using npm testing and execution discipline in the workflow
With the basic setup complete, the `npm test` command becomes your daily ally . Running it before committing changes should be almost automatic, like saving the file or running the linter.
Many teams adopt the unwritten rule of not committing if the tests fail . This practice prevents the main project branch from breaking and maintains a minimum guaranteed quality for every merge or pull request added to the repository.
Jest also offers a very useful interactive mode for development . By running `npm test` in watch mode, the framework reruns only the tests related to the files you modify, which greatly speeds up the test-and-fix cycle while developing new features or fixing bugs.
Integrating Jest into a continuous integration (CI) system like GitHub Actions completes the cycle . Every time someone pushes code to the remote repository, the CI server runs `npm test` and blocks the integration if the suite fails, preventing errors from creeping into shared environments.
Code coverage: measuring how much is actually tested
It's not enough to simply have some tests written. It's also important to know the extent to which they cover the code . For this, Jest integrates coverage reporting, which indicates which lines, functions, and branches were executed during testing.
Generating these reports is as simple as adding the `--coverage` flag to the `test` command . For example, you can configure the `test` script in `package.json` as "test": `jest --coverage` or run `npm test --coverage` whenever you want a detailed report.
The results include coverage percentages per file and globally . You'll see which files have good coverage and which are barely touched during testing, which helps you decide where it's worth investing additional effort in writing more tests.
However, it's important to remember that 100% coverage doesn't guarantee the absence of errors . It's possible to run every line of code with undemanding tests, so the quality of the assertions is just as, if not more, important than the number itself.
Using coverage as a rough indicator, combined with code reviews and common sense , is a good way to maintain a balance between testing effort and real benefits for project stability.
With all this in mind, using Jest and tools like the React Testing Library in frontend projects becomes a pretty logical decision : it allows you to check that each component and function does what it should, easily run tests with npm test, monitor coverage with –coverage, and maintain a solid codebase where it's much safer to evolve the product without fear of breaking what already worked.



