Skip to main content

Unit and Integration Testing

Unit testing checks one isolated part, while integration testing checks multiple parts working together.

One function or component  -> Unit Test
Connected modules or UI -> Integration Test

One-line idea: Unit tests prove that the pieces work; integration tests prove that the pieces work together.

Core Concepts​

What Is Unit Testing?​

Unit testing verifies a small, independent unit of code in isolation.

A unit can be:

  • a JavaScript function
  • a React component
  • a utility
  • a reducer
  • a validation rule

Example questions:

Does this sorting function return the correct order?
Does this button render the correct label?
Does this validator reject an empty email?

Unit tests are usually:

  • small
  • fast
  • predictable
  • independent from external systems

What Is Integration Testing?​

Integration testing verifies that multiple units interact correctly.

It can check interactions between:

  • parent and child components
  • form fields and validation logic
  • a component and an API layer
  • multiple modules
  • UI actions and state updates

Example questions:

Does clicking Add to Cart update the cart count?
Does submitting the form display validation errors?
Does loaded API data appear in the component?

Unit vs Integration Testing​

AreaUnit TestingIntegration Testing
ScopeOne isolated unitMultiple connected units
GoalVerify individual behaviorVerify interaction between parts
DependenciesUsually avoided or replacedSome real dependencies work together
SpeedUsually very fastUsually slower than a unit test
Failure meaningA specific unit is incorrectThe connection or combined behavior is incorrect
ExampleTest sortUsersByAge()Test button → state update → UI change
Unit test
[ Function ]

Integration test
[ Component ] <-> [ State ] <-> [ Child Component ]

What Is Component Testing?​

Component testing verifies a UI component's rendering and interaction.

It may be:

  • a unit test when one small component is tested in isolation
  • an integration test when the component interacts with children, state, context, routing or another module

The label depends on the test scope, not only on the tool being used.

Testing Tools and Their Roles​

ToolRole
JestRuns tests and provides assertions
JSDOMSimulates a browser-like DOM inside Node.js
React Testing LibraryTests rendered UI through user-visible behavior
VitestFast alternative test runner
CypressCommonly used for integration and E2E browser tests
PlaywrightAutomates real browsers for application flows

Jest can be used for both unit and integration tests.

JSDOM allows DOM-based tests to run without opening a real browser.

React Testing Library encourages testing what users can see and do instead of testing internal implementation details.

Practical Testing Examples​

Basic Jest Setup​

Create a project and install Jest:

npm init -y
npm install --save-dev jest

Add a test script to package.json:

package.json
{
"scripts": {
"test": "jest"
}
}

Run the test suite:

npm test

Test File Naming​

Common naming conventions are:

app.test.js
app.spec.js

A test file is usually placed near the source file or inside a dedicated test directory.

src/
├── app.js
└── app.test.js

Unit Test: Sorting Users by Age​

Create a pure function that receives users and returns them in descending age order.

app.js
function sortUsersByAge(users) {
return [...users].sort((a, b) => b.age - a.age);
}

module.exports = sortUsersByAge;

[...users] creates a copy so the original array is not changed by sort().

Now test the function:

app.test.js
const sortUsersByAge = require("./app");

const users = [
{ name: "Akshay", age: 28 },
{ name: "Simran", age: 30 },
{ name: "Sachin", age: 50 },
{ name: "Aman", age: 20 },
];

test("places the oldest user first", () => {
const sortedUsers = sortUsersByAge(users);

expect(sortedUsers[0].name).toBe("Sachin");
});

test("places the youngest user last", () => {
const sortedUsers = sortUsersByAge(users);

expect(sortedUsers[sortedUsers.length - 1].name).toBe("Aman");
});

test("keeps all users in the result", () => {
const sortedUsers = sortUsersByAge(users);

expect(sortedUsers).toHaveLength(4);
});

Understand the Jest Test Structure​

test("description of expected behavior", () => {
const result = functionUnderTest();

expect(result).toBe(expectedValue);
});
Arrange -> prepare input
Act -> run the unit
Assert -> check the result

Applied to the sorting test:

Arrange -> create the users array
Act -> call sortUsersByAge(users)
Assert -> verify the first, last and total items

Common Jest Matchers​

MatcherUseExample
toBe()Primitive or exact valueexpect(count).toBe(2)
toEqual()Object or array structureexpect(user).toEqual(expectedUser)
toBeTruthy()Truthy resultexpect(isValid).toBeTruthy()
toBeFalsy()Falsy resultexpect(hasError).toBeFalsy()
toContain()Item inside an array or textexpect(names).toContain("Aman")
toHaveLength()Array or string lengthexpect(users).toHaveLength(4)

Unit Test: A Small React Component​

The component receives a value and renders it.

Greeting.jsx
export default function Greeting({ name }) {
return <h1>Hello, {name}</h1>;
}
Greeting.test.jsx
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import Greeting from "./Greeting";

test("renders the supplied name", () => {
render(<Greeting name="Aman" />);

expect(
screen.getByRole("heading", { name: "Hello, Aman" }),
).toBeInTheDocument();
});

This remains a focused unit-style component test because it checks one component with simple props.

Integration Test: Cart Components Working Together​

Suppose one component adds a product and another displays the current count.

CartPage.jsx
import { useState } from "react";

function CartCount({ count }) {
return <p>Cart items: {count}</p>;
}

function AddToCartButton({ onAdd }) {
return <button onClick={onAdd}>Add to Cart</button>;
}

export default function CartPage() {
const [count, setCount] = useState(0);

return (
<main>
<CartCount count={count} />
<AddToCartButton onAdd={() => setCount((value) => value + 1)} />
</main>
);
}

Test the complete interaction:

CartPage.test.jsx
import { fireEvent, render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import CartPage from "./CartPage";

test("updates the cart count after adding an item", () => {
render(<CartPage />);

expect(screen.getByText("Cart items: 0")).toBeInTheDocument();

fireEvent.click(screen.getByRole("button", { name: "Add to Cart" }));

expect(screen.getByText("Cart items: 1")).toBeInTheDocument();
});

This is an integration test because it verifies the connection between:

Button click
↓
Parent state update
↓
New count passed as a prop
↓
CartCount renders the updated value

Test User-Visible Behavior​

Prefer assertions based on behavior visible to the user.

Prefer:

screen.getByRole("button", { name: "Add to Cart" });
screen.getByText("Cart items: 1");

Avoid depending heavily on:

internal state variables
private helper calls
component implementation details

Tests based on visible behavior are more likely to remain valid when internal code is refactored.

When to Use Each Type​

Use unit tests for:

  • pure functions
  • calculation logic
  • validation rules
  • reducers
  • small focused components

Use integration tests for:

  • parent-child behavior
  • form submission and validation
  • component-state interaction
  • modules sharing data
  • UI and API interaction

Use both when a feature contains important individual logic and important connections between parts.

Benefits, Trade-Offs and Common Mistakes​

AreaUnit TestsIntegration Tests
Main strengthFast and precise feedbackHigher confidence in real feature behavior
Main limitationMay miss connection problemsFailures can be harder to locate
MaintenanceUsually small and focusedCan require more setup and test data

Common mistakes

MistakeBetter approach
Testing several modules but calling it a unit testName the test by its real scope
Depending on an external system in every unit testKeep unit tests isolated
Testing React internalsTest rendered output and user interactions
Writing only unit testsAdd integration tests for important connections
Writing only large integration testsUnit-test important isolated logic too
Using vague test namesDescribe the expected behavior clearly
Mutating shared test dataCreate fresh data or copy before mutation

Summary​

Unit tests verify individual pieces, while integration tests verify that connected pieces produce the correct feature behavior.

Isolate one function or component
↓
Verify its input and output
↓
Combine related parts
↓
Perform a user-visible action
↓
Verify the complete connected result