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
| Area | Unit Testing | Integration Testing |
|---|---|---|
| Scope | One isolated unit | Multiple connected units |
| Goal | Verify individual behavior | Verify interaction between parts |
| Dependencies | Usually avoided or replaced | Some real dependencies work together |
| Speed | Usually very fast | Usually slower than a unit test |
| Failure meaning | A specific unit is incorrect | The connection or combined behavior is incorrect |
| Example | Test 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
| Tool | Role |
|---|---|
| Jest | Runs tests and provides assertions |
| JSDOM | Simulates a browser-like DOM inside Node.js |
| React Testing Library | Tests rendered UI through user-visible behavior |
| Vitest | Fast alternative test runner |
| Cypress | Commonly used for integration and E2E browser tests |
| Playwright | Automates 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:
{
"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.
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:
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
| Matcher | Use | Example |
|---|---|---|
toBe() | Primitive or exact value | expect(count).toBe(2) |
toEqual() | Object or array structure | expect(user).toEqual(expectedUser) |
toBeTruthy() | Truthy result | expect(isValid).toBeTruthy() |
toBeFalsy() | Falsy result | expect(hasError).toBeFalsy() |
toContain() | Item inside an array or text | expect(names).toContain("Aman") |
toHaveLength() | Array or string length | expect(users).toHaveLength(4) |
Unit Test: A Small React Component
The component receives a value and renders it.
export default function Greeting({ name }) {
return <h1>Hello, {name}</h1>;
}
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.
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:
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
| Area | Unit Tests | Integration Tests |
|---|---|---|
| Main strength | Fast and precise feedback | Higher confidence in real feature behavior |
| Main limitation | May miss connection problems | Failures can be harder to locate |
| Maintenance | Usually small and focused | Can require more setup and test data |
Common mistakes
| Mistake | Better approach |
|---|---|
| Testing several modules but calling it a unit test | Name the test by its real scope |
| Depending on an external system in every unit test | Keep unit tests isolated |
| Testing React internals | Test rendered output and user interactions |
| Writing only unit tests | Add integration tests for important connections |
| Writing only large integration tests | Unit-test important isolated logic too |
| Using vague test names | Describe the expected behavior clearly |
| Mutating shared test data | Create fresh data or copy before mutation |
Interview Revision
Quick Revision Checklist
- Unit testing checks one function or component in isolation.
- Integration testing checks multiple units working together.
- Unit tests are usually faster and more focused.
- Integration tests provide confidence in connected behavior.
- Jest runs tests and provides assertions.
- JSDOM simulates a DOM environment inside Node.js.
- React Testing Library focuses on visible behavior and interactions.
- Test files commonly use
.test.jsor.spec.js. test()defines a test case.expect()creates an assertion.- Arrange, Act and Assert is a useful test structure.
- A component test may be unit or integration depending on its scope.
- Use unit and integration tests together for balanced confidence.
Frequently Asked Interview Questions
1. What is unit testing?
Unit testing verifies one small function, component or module in isolation.
2. What is integration testing?
Integration testing verifies that multiple units communicate and behave correctly together.
3. What is the main difference between unit and integration testing?
The difference is scope. Unit tests check one part; integration tests check connections between parts.
4. Can React components have unit tests?
Yes. A small component tested with simple props and no significant external interaction can be treated as a unit.
5. What is JSDOM?
JSDOM provides a browser-like DOM environment in Node.js so UI tests can render and query elements without opening a real browser.
6. Why use React Testing Library?
It encourages testing the UI through rendered content, accessible queries and user interactions rather than component internals.
7. Can Jest run integration tests?
Yes. Jest is a test runner and assertion library; the test type depends on how many units the test exercises.
8. Why do we need both unit and integration tests?
Unit tests catch mistakes in isolated logic, while integration tests catch problems in the way parts communicate.
9. What are common Jest matchers?
Common matchers include toBe, toEqual, toBeTruthy, toBeFalsy, toContain and toHaveLength.
Memory Trick
Piece -> Connection
Unit = test one piece
Integration = test the connection between pieces
One-Line Summary
Unit tests verify individual pieces, while integration tests verify that connected pieces produce the correct feature behavior.
Final Mental Model
Isolate one function or component
↓
Verify its input and output
↓
Combine related parts
↓
Perform a user-visible action
↓
Verify the complete connected result