FroquizFroquiz
HomeQuizzesSenior ChallengeGet CertifiedBlogAbout
Sign InStart Quiz
Sign InStart Quiz
Froquiz

The most comprehensive quiz platform for software engineers. Test yourself with 10000+ questions and advance your career.

LinkedIn

Platform

  • Start Quizzes
  • Topics
  • Blog
  • My Profile
  • Sign In

About

  • About Us
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

Β© 2026 Froquiz. All rights reserved.Built with passion for technology
Blog & Articles

JavaScript Testing with Jest: Unit Tests, Mocks, Coverage and Best Practices

Learn how to write effective JavaScript tests with Jest. Covers unit testing, mocking modules and APIs, async tests, snapshot testing, code coverage, and testing React components.

Yusuf SeyitoğluMarch 11, 20262 views10 min read

JavaScript Testing with Jest: Unit Tests, Mocks, Coverage and Best Practices

Writing tests is one of the clearest signals of code quality and professional maturity. Jest is the most widely used JavaScript testing framework β€” it works for plain JavaScript, Node.js APIs, and React components. This guide teaches you to write tests that are fast, reliable, and actually useful.

Why Testing Matters

Tests catch regressions (bugs you accidentally reintroduce), document expected behavior, and give you confidence to refactor. Good tests are an investment that pays back every time you change code without breaking production.

Jest Basics

bash
npm install --save-dev jest
javascript
// math.js export function add(a, b) { return a + b; } export function divide(a, b) { if (b === 0) throw new Error("Division by zero"); return a / b; } // math.test.js import { add, divide } from "./math"; describe("math utilities", () => { describe("add", () => { test("adds two positive numbers", () => { expect(add(1, 2)).toBe(3); }); test("adds negative numbers", () => { expect(add(-1, -2)).toBe(-3); }); test("handles zero", () => { expect(add(5, 0)).toBe(5); }); }); describe("divide", () => { test("divides correctly", () => { expect(divide(10, 2)).toBe(5); }); test("throws on division by zero", () => { expect(() => divide(10, 0)).toThrow("Division by zero"); }); }); });

Run tests:

bash
npx jest npx jest --watch # re-run on file change npx jest math.test.js # run specific file npx jest --coverage # include coverage report

Common Matchers

javascript
// Equality expect(value).toBe(42); // strict equality (===) expect(obj).toEqual({ a: 1 }); // deep equality for objects/arrays expect(value).not.toBe(null); // Truthiness expect(value).toBeTruthy(); expect(value).toBeFalsy(); expect(value).toBeNull(); expect(value).toBeUndefined(); expect(value).toBeDefined(); // Numbers expect(0.1 + 0.2).toBeCloseTo(0.3); // floating point expect(value).toBeGreaterThan(3); expect(value).toBeLessThanOrEqual(10); // Strings expect("Hello World").toContain("World"); expect("hello").toMatch(/^hel/); // Arrays expect([1, 2, 3]).toContain(2); expect([1, 2, 3]).toHaveLength(3); expect(arr).toEqual(expect.arrayContaining([1, 3])); // Objects expect(obj).toHaveProperty("name", "Alice"); expect(obj).toMatchObject({ name: "Alice" }); // partial match // Errors expect(() => riskyFn()).toThrow(); expect(() => riskyFn()).toThrow("specific message"); expect(() => riskyFn()).toThrow(TypeError);

Testing Async Code

Promises and async/await

javascript
import { fetchUser } from "./api"; // With async/await (cleanest) test("fetches user by id", async () => { const user = await fetchUser(1); expect(user).toHaveProperty("name"); expect(user.id).toBe(1); }); // Test that a promise rejects test("throws on invalid id", async () => { await expect(fetchUser(-1)).rejects.toThrow("Invalid ID"); }); // With .resolves / .rejects matchers test("resolves with user data", () => { return expect(fetchUser(1)).resolves.toMatchObject({ id: 1 }); });

Always return or await async assertions β€” if you forget, the test passes before the assertion runs.

Mocking

jest.fn()

Create a mock function that records calls:

javascript
test("calls the callback with the result", () => { const callback = jest.fn(); processData([1, 2, 3], callback); expect(callback).toHaveBeenCalledTimes(3); expect(callback).toHaveBeenCalledWith(1); expect(callback).toHaveBeenLastCalledWith(3); }); // Mock return value const mockFn = jest.fn().mockReturnValue(42); const mockFnOnce = jest.fn() .mockReturnValueOnce("first") .mockReturnValueOnce("second") .mockReturnValue("default");

jest.mock() β€” mock entire modules

javascript
// emailService.js export async function sendEmail(to, subject, body) { // real implementation calls an email API } // user.service.test.js import { sendEmail } from "./emailService"; import { registerUser } from "./user.service"; jest.mock("./emailService"); // replaces module with auto-mock test("sends welcome email after registration", async () => { sendEmail.mockResolvedValue({ success: true }); await registerUser({ email: "alice@example.com", password: "secret" }); expect(sendEmail).toHaveBeenCalledWith( "alice@example.com", "Welcome!", expect.stringContaining("alice@example.com") ); }); afterEach(() => { jest.clearAllMocks(); // reset call counts between tests });

Mocking fetch / HTTP

javascript
global.fetch = jest.fn(); test("fetches and parses JSON", async () => { fetch.mockResolvedValue({ ok: true, json: async () => ({ id: 1, name: "Alice" }), }); const user = await getUser(1); expect(fetch).toHaveBeenCalledWith("/api/users/1"); expect(user.name).toBe("Alice"); });

Spying on existing methods

javascript
import * as db from "./database"; test("calls db.save with the right data", async () => { const saveSpy = jest.spyOn(db, "save").mockResolvedValue({ id: 1 }); await createUser({ name: "Alice" }); expect(saveSpy).toHaveBeenCalledWith( expect.objectContaining({ name: "Alice" }) ); saveSpy.mockRestore(); // restore original implementation });

Setup and Teardown

javascript
describe("UserService", () => { let service; beforeAll(async () => { // runs once before all tests in this describe await db.connect(); }); afterAll(async () => { // runs once after all tests in this describe await db.disconnect(); }); beforeEach(() => { // runs before each test service = new UserService(); }); afterEach(() => { // runs after each test -- clean up mocks, state jest.clearAllMocks(); }); test("creates a user", async () => { ... }); });

Testing React Components

bash
npm install --save-dev @testing-library/react @testing-library/jest-dom
jsx
// Button.jsx export function Button({ label, onClick, disabled = false }) { return ( <button onClick={onClick} disabled={disabled}> {label} </button> ); } // Button.test.jsx import { render, screen, fireEvent } from "@testing-library/react"; import { Button } from "./Button"; test("renders button with label", () => { render(<Button label="Click me" onClick={() => {}} />); expect(screen.getByText("Click me")).toBeInTheDocument(); }); test("calls onClick when clicked", () => { const handleClick = jest.fn(); render(<Button label="Submit" onClick={handleClick} />); fireEvent.click(screen.getByText("Submit")); expect(handleClick).toHaveBeenCalledTimes(1); }); test("is disabled when disabled prop is true", () => { render(<Button label="Save" onClick={() => {}} disabled />); expect(screen.getByText("Save")).toBeDisabled(); });

Testing async React components

jsx
import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { UserProfile } from "./UserProfile"; jest.mock("./api", () => ({ fetchUser: jest.fn().mockResolvedValue({ id: 1, name: "Alice" }), })); test("displays user name after loading", async () => { render(<UserProfile userId={1} />); expect(screen.getByText("Loading...")).toBeInTheDocument(); await waitFor(() => { expect(screen.getByText("Alice")).toBeInTheDocument(); }); });

Code Coverage

bash
npx jest --coverage

Jest generates a coverage report showing which lines, branches, and functions were exercised:

code
File | % Stmts | % Branch | % Funcs | % Lines -------------|---------|----------|---------|-------- math.js | 100 | 100 | 100 | 100 user.service | 85 | 72 | 90 | 85

A reasonable coverage target for business-critical code is 80%+. Do not chase 100% β€” some code is not worth testing.

Testing Best Practices

Name tests as specifications:

javascript
// Bad test("test 1", () => { ... }); // Good test("sends a password reset email when the user exists", () => { ... }); test("throws UserNotFoundError when the email is not registered", () => { ... });

One assertion per concept β€” tests should tell you exactly what failed:

javascript
// Bad: hard to know which assertion failed test("user creation", async () => { const user = await createUser(data); expect(user.id).toBeDefined(); expect(user.email).toBe("alice@example.com"); expect(user.createdAt).toBeDefined(); expect(mockEmail).toHaveBeenCalled(); }); // Better: split by concern test("returns a user with an id", async () => { ... }); test("sends a welcome email", async () => { ... });

Avoid testing implementation details β€” test behavior, not internals. If you refactor the internals and all tests still pass, that is a good sign.

Practice on Froquiz

Testing knowledge is increasingly expected at mid-level and senior developer interviews. Test your JavaScript and Node.js knowledge on Froquiz β€” we cover async, OOP, and modern JavaScript concepts.

Summary

  • describe groups related tests; test/it defines a single test case
  • Use toBe for primitives; toEqual for objects and arrays (deep equality)
  • Always await or return async assertions β€” forgotten awaits cause false passes
  • jest.mock() replaces entire modules; jest.spyOn() intercepts specific methods
  • beforeEach/afterEach set up and tear down per-test state; beforeAll/afterAll for expensive shared setup
  • Test React components with Testing Library β€” query by accessible roles and text, not implementation
  • Name tests as specifications β€” a failing test should clearly describe what is broken

About Author

Yusuf Seyitoğlu

Author β†’

Other Posts

  • System Design Fundamentals: Scalability, Load Balancing, Caching and DatabasesMar 12
  • CSS Advanced Techniques: Custom Properties, Container Queries, Grid Masonry and Modern LayoutsMar 12
  • GraphQL Schema Design: Types, Resolvers, Mutations and Best PracticesMar 12
All Blogs