How We Use Jest for Unit Testing in JavaScript Applications

Building

Quick summary

Build reliable JavaScript applications with Jest unit testing, API mocking, and code coverage.

Introduction

Reliable JavaScript applications need more than functional features and a successful build. As an application grows, a small change in one function can unexpectedly affect another part of the system. Jest for unit testing in JavaScript applications provides a practical way to catch these issues early by testing individual pieces of application logic in isolation.

In our development workflow, we use Jest to verify business logic, validate expected behaviours, mock external dependencies, and identify regressions before code reaches production. Its built-in assertions, mocking capabilities, and coverage reporting make it suitable for both small projects and larger JavaScript and TypeScript codebases.

In this guide, we’ll walk through our approach to JavaScript unit testing with Jest, including project setup, test organization, practical Jest test cases, API mocking, code coverage, performance optimization, and mistakes we encountered while building and maintaining test suites.

1. Why We Use Jest for JavaScript Unit Testing

There are several JavaScript testing tools available today, but Jest has become a popular choice for teams that want an integrated testing experience.

1. Simple Configuration  

One of the biggest advantages of the Jest testing framework is its relatively simple setup.

A basic project can start running tests with only a few configuration options. Jest can automatically discover test files based on common naming conventions such as:

*.test.js
*.spec.js
*.test.ts
*.spec.ts

This reduces the amount of testing infrastructure developers need to maintain themselves.

2. Fast Test Execution  

Jest is designed to execute tests efficiently and can run tests in parallel across workers. This becomes especially useful when a project contains hundreds or thousands of tests.

Developers can also run only the tests related to recently changed files during development, while the complete suite can run in CI/CD.

3. Built-in Mocking and Assertions  

Jest provides built-in functionality for creating mock functions, mocking modules, spying on calls, and making assertions.

For example:

There is no need to install a separate mocking library for many common testing scenarios.

4. Code Coverage Reporting  

Jest can generate coverage information for statements, branches, functions, and lines.

Running:

can provide a useful overview of which parts of an application are covered by tests.

Coverage should not be treated as the only measurement of test quality, but it can help identify areas that have been completely overlooked.

5. JavaScript and TypeScript Support  

Jest can be used in JavaScript applications as well as TypeScript projects with the appropriate transformation or runtime configuration.

This makes it practical for teams gradually migrating JavaScript applications to TypeScript or maintaining mixed codebases.

Jest vs Mocha vs Vitest

FeatureJestMochaVitest
Test runnerBuilt-inBuilt-inBuilt-in
AssertionsBuilt-inUsually paired with another libraryBuilt-in
MockingBuilt-inOften requires additional toolsBuilt-in
CoverageBuilt-in integrationAdditional setup commonly usedBuilt-in integration
TypeScriptSupported with configurationSupported with configurationStrong TypeScript/Vite integration
ConfigurationGenerally simpleFlexible but more manualVery simple for Vite projects
Best fitGeneral JS/TS applicationsHighly customizable testing stacksVite-based modern applications

The right choice depends on the application and development ecosystem. Jest remains a strong option when a team wants a mature, integrated testing framework with a broad set of features.

2. Our Jest Setup and Test Structure

A good test suite is not only about writing assertions. Test organization becomes increasingly important as the application grows.

Installing and Configuring Jest  

For a JavaScript project, Jest can be installed as a development dependency:

Tests can be executed with:

For projects requiring additional configuration, a Jest configuration file can be created.

For example:

The exact configuration should depend on the application. React applications, Node.js services, and TypeScript projects may require additional settings.

Recommended Folder and File Structure  

We prefer keeping tests close to the code they validate when that makes the project easier to navigate.

For example:

Another valid approach is to keep all tests in a dedicated tests directory.

The important point is consistency. Developers should be able to find the tests for a piece of application code without searching through the entire repository.

Naming Conventions  

Clear naming makes Jest test cases easier to understand.

We commonly use:

priceService.test.js
validation.test.js
ProductCard.test.jsx

Test names should describe behaviour rather than implementation.

Prefer:

The first test describes what the application should do. The second focuses on how the implementation currently works.

Separating Test Data, Mocks, and Test Cases  

As the test suite grows, large amounts of test data can make individual test files difficult to read.

We therefore separate reusable fixtures and mocks when appropriate:

This keeps the actual Jest test cases focused on setup, behaviour, and assertions.

Jest Testing Flow  

A typical unit test follows this flow:

Application code
       ↓
   Jest test
       ↓
Mock dependencies
       ↓
    Assertion
       ↓
   Test result

The objective is to isolate the unit being tested while replacing external dependencies that are not part of the current test.

3. How We Use Jest for Unit Testing in JavaScript Applications

The easiest way to understand How to use Jest for unit testing in JavaScript applications is to work through a practical feature.

Consider a price-calculation function for an e-commerce application.

Application Function  

The function has several behaviours that should be tested:

  • A product with no discount

  • A product with a valid discount

  • A 100% discount

  • A negative price

  • An invalid discount percentage

Basic Unit Test  

The simplest Jest test checks the expected result for a normal scenario:

This test follows a simple pattern:

Arrange → Act → Assert

We provide the input, execute the function, and verify the output.

Positive Scenario  

We should also test different valid inputs:

These tests verify expected behaviour under normal conditions.

Negative Scenario  

Good unit testing should also verify how the application handles invalid input.

Edge-Case Validation  

Edge cases are often where bugs appear.

For example:

These tests make the expected boundaries explicit.

Organizing Jest Test Cases  

When learning How to write and organize Jest test cases in real projects, grouping related behaviours with describe() makes the suite easier to understand.

This structure becomes particularly useful when a feature contains multiple related behaviours.

4. Mocking APIs and Dependencies

Unit tests should ideally focus on one unit of behaviour.

If the function being tested calls an API, database, file system, or external service, directly using that dependency can make the test slow and unreliable.

Jest provides several ways to isolate dependencies.

Mock Functions with jest.fn()  

jest.fn() creates a mock function that can track calls and control return values.

Mock functions are useful when the dependency itself does not need to be tested.

Mock Modules with jest.mock()  

Suppose an application uses an API client:

The API client can be mocked:

The test does not make a real HTTP request.

Testing Asynchronous Functions  

Modern JavaScript applications frequently depend on asynchronous operations.

Jest supports async/await naturally:

Keep Unit Tests Independent  

A unit test should not depend on:

  • A real database

  • A live API

  • Another test running first

  • Shared mutable state

  • A particular execution order

Mocking external dependencies helps make tests deterministic and significantly easier to run in CI/CD environments.

5. Code Coverage and Test Performance

Jest code coverage gives developers visibility into how much of their code is exercised by tests.

A coverage report can measure:

  • Statements

  • Branches

  • Functions

  • Lines

For example:

A report might show:

File                 % Stmts   % Branch   % Funcs   % Lines
———————————————————–
priceService.js         100        90       100       100
validation.js            95        88        90        95
apiService.js             82        70        85        82

6. Focus on Meaningful Coverage Gaps

A high percentage does not automatically mean a test suite is good.

For example, a test might execute a line without actually verifying meaningful behaviour.

Instead of asking:

“How can we reach 100%?”

we should ask:

“Which important behaviours are not currently protected by tests?”

Branch coverage can be particularly useful because it can reveal untested error paths and conditional logic.

7. Avoid Chasing 100% Coverage Without Purpose

A project with 100% statement coverage can still contain bugs.

Tests should prioritize:

  • Business-critical logic

  • Error handling

  • Boundary conditions

  • Frequently changed code

  • Regression-prone functionality

  • Important user workflows

Coverage is a measurement tool, not the definition of test quality.

8. Keeping the Jest Suite Fast

As the number of Jest test cases increases, execution time can become a development concern.

Some practices that help include:

  1. Mock external services.

  2. Avoid unnecessary integration work inside unit tests.

  3. Keep test setup lightweight.

  4. Avoid excessive global setup.

  5. Run targeted tests during development.

  6. Remove redundant test cases.

  7. Keep tests independent.

9. Benchmark: Before and After Optimization

A benchmark should ideally be taken from your own CI/local environment because execution time depends on hardware, Node.js version, Jest configuration, and project size.

For illustration, a team could track results like this:

MetricBefore OptimizationAfter Optimization
Jest test cases420420
Execution time48 sec29 sec
Statement coverage78%86%
Branch coverage65%79%
Functions covered81%89%

The important lesson is not simply reducing execution time. A useful optimization should make the test suite faster without sacrificing meaningful coverage or reliability.

10. Real Jest Mistakes We Encountered  

Writing tests is not enough. The way tests are written can also introduce maintenance problems.

Here are some common issues we encountered.

1. Testing Implementation Details Instead of Behaviour  

Tests that depend heavily on internal implementation can break whenever code is refactored—even if the application’s behaviour remains unchanged.

Prefer:

expect(result).toBe(800);

over testing every internal function call unless that interaction is itself important behaviour.

2. Using Too Many Mocks  

Mocks are useful, but excessive mocking can make tests disconnected from reality.

If everything is mocked, a test may pass even though the actual pieces of the

application do not work correctly together.

A good rule is to mock external boundaries and dependencies that are irrelevant to the behaviour under test.

3. Writing Multiple Behaviours in One Test  

A test should ideally verify one clear behaviour.

Avoid creating a test that simultaneously checks:

  • Authentication

  • User creation

  • Database updates

  • Email notifications

  • Response formatting

Breaking these behaviours into focused tests makes failures easier to understand.

4. Forgetting to Clear Mocks  

Mock state can leak between tests when mocks are reused.

Depending on the situation, Jest provides:

jest.clearAllMocks();

or configuration options such as:

clearMocks: true

The correct approach depends on how the test suite is structured.

5. Creating Tests That Depend on Execution Order  

Each test should be capable of running independently.

Bad pattern:

Test 1 creates data
       ↓
Test 2 expects Test 1’s data

Better:

Test 1 → creates its own data
Test 2 → creates its own data

This prevents tests from passing individually but failing when the entire suite runs.

6. Ignoring Error and Edge Cases

Testing only successful scenarios creates a false sense of confidence.

For each important function, consider:

  • What happens with valid input?

  • What happens with invalid input?

  • What happens when an external dependency fails?

  • What happens with empty input?

  • What happens at the minimum and maximum boundaries?

This is where many valuable Jest test cases come from.

11. Our Jest Unit Testing Checklist

Before considering a unit test complete, we use a checklist like this:

  • Tests one clear behaviour.

  • Uses a descriptive test name.

  • Covers the expected success scenario.

  • Covers relevant failure scenarios.

  • Covers important edge cases.

  • Mocks only external or unnecessary dependencies.

  • Does not depend on another test.

  • Does not depend on execution order.

  • Produces deterministic results.

  • Uses meaningful assertions.

  • Can run reliably in CI/CD.

  • Adds value rather than simply increasing coverage.

This checklist is especially useful during code reviews because it shifts the discussion from “Did we add tests?” to “Did we add the right tests?”

Conclusion

A strong unit-testing strategy is an important part of maintaining reliable JavaScript applications. Jest for unit testing in JavaScript applications gives development teams a practical combination of test execution, assertions, mocking, and coverage reporting without requiring a large testing infrastructure.

Our approach is to focus on behaviour, isolate external dependencies, test both expected and unexpected scenarios, and keep the test suite independent and fast. We also use Jest code coverage as a guide for finding gaps rather than treating a particular percentage as the definition of quality.

Whether you are introducing testing to an existing JavaScript application or building a new TypeScript project, JavaScript unit testing with Jest can provide an effective foundation for catching defects early and keeping production code reliable.

The goal is not to write the largest possible number of tests. The goal is to create a maintainable set of Jest test cases that gives developers confidence when they change, refactor, and ship the application.

FAQs

Jest is a JavaScript testing framework used to write and execute automated tests. It is commonly used for unit testing functions, services, components, and application logic. It also provides built-in assertions, mocking capabilities, and code coverage reporting.
Yes. Jest can be used with both JavaScript and TypeScript projects. TypeScript projects generally require appropriate configuration or tooling so that Jest can execute TypeScript test files and application code.

Create a test file and use Jest’s test() or it() function together with an assertion.

function add(a, b) {
  return a + b;
}

test(“adds two numbers”, () => {
  expect(add(2, 3)).toBe(5);
});

Run the test using:

npm test

You can mock an API module using jest.mock() and configure the mocked function to return the response required by the test.

jest.mock(“./apiClient”);

apiClient.get.mockResolvedValue({
  data: {
    id: 1
  }
});

This keeps the unit test independent of the real API.

There is no universal percentage that guarantees good testing. A commonly used target is around 80% or higher, but teams should choose thresholds based on project risk and requirements.

More important than the percentage is whether critical business logic, failure scenarios, branches, and edge cases are properly tested.

Jest is commonly used for unit and component-level testing, while Cypress is primarily used for browser-based end-to-end and application-level testing.

Jest can test an individual function such as:

calculateFinalPrice(1000, 20);

Cypress can test a broader user interaction, such as opening an application, filling out a form, submitting it, and verifying the result in the browser.

The two tools can complement each other rather than being direct replacements.

This usually indicates that tests are sharing state or have another hidden dependency.

Common causes include:

  • Mocks not being cleared.

  • Shared mutable objects.

  • Global state leaking between tests.

  • Temporary files not being cleaned up.

  • Tests depending on execution order.

  • Asynchronous operations not being properly awaited.

Keeping tests isolated and resetting shared state between tests can prevent these problems.
Author : Divya John Date: October 2, 2026