Eduzan / Mern Stack
Testing MERN Applications
What is Testing?
Testing is the process of verifying that an application works as expected and continues to work when changes are made. In a MERN application, testing ensures that:
- React components render correctly
- Express APIs return correct responses
- MongoDB operations behave as expected
- Frontend and backend work together seamlessly
Why Testing is Important
- Detects bugs early
- Prevents regressions
- Improves code quality
- Makes refactoring safer
- Essential for CI/CD pipelines
- Builds confidence in deployments
In production-grade MERN apps, testing is not optional.
| Testing Type | Focus |
|---|---|
| Unit Testing | Individual functions/components |
| Integration Testing | Interaction between modules |
| End-to-End (E2E) | Full user flow |
| Component Testing | UI behavior |
| API Testing | Backend routes |
Unit testing tests small, isolated pieces of code, such as:
- Utility functions
- Redux reducers
- React components
- Backend service functions
Why Jest?
- Zero configuration (with React)
- Fast
- Snapshot testing
- Built-in mocking
Install:
npm install --save-dev jest
Simple Jest Test Example
// sum.js
export function sum(a, b) {
return a + b;
}
// sum.test.js
import { sum } from "./sum";
test("adds two numbers", () => {
expect(sum(2, 3)).toBe(5);
});
Run:
npm test
Install:
npm install --save-dev @testing-library/react @testing-library/jest-dom
Example React Component
function Greeting({ name }) {
return <h1>Hello {name}</h1>;
}
Test Case
import { render, screen } from "@testing-library/react";
import Greeting from "./Greeting";
test("renders greeting message", () => {
render(<Greeting name="John" />);
expect(screen.getByText("Hello John")).toBeInTheDocument();
});
Mocha is commonly used for backend testing.
Install:
npm install --save-dev mocha chai
Example:
import { expect } from "chai";
describe("Math test", () => {
it("should add numbers", () => {
expect(2 + 3).to.equal(5);
});
});
Integration testing checks how multiple components work together, such as:
- Express routes + MongoDB
- Middleware + controllers
Supertest allows testing Express APIs without starting a real server.
Install:
npm install --save-dev supertest
Express App Example
// app.js
import express from "express";
const app = express();
app.use(express.json());
app.get("/api/health", (req, res) => {
res.json({ status: "ok" });
});
export default app;
Integration Test
import request from "supertest";
import app from "../app.js";
describe("GET /api/health", () => {
it("should return health status", async () => {
const res = await request(app).get("/api/health");
expect(res.status).toBe(200);
expect(res.body.status).toBe("ok");
});
});
import { expect } from "chai";
expect(res.body).to.have.property("status");
request(app)
.get("/api/profile")
.set("Authorization", `Bearer ${token}`)
E2E testing simulates real user behavior:
- Opening the browser
- Clicking buttons
- Filling forms
- Submitting data
- Receiving responses
Why Cypress?
- Easy setup
- Real browser testing
- Excellent debugging tools
- Fast execution
Install:
npm install --save-dev cypress
Open:
npx cypress open
Cypress Test Example (Login Flow)
describe("Login Test", () => {
it("logs in the user", () => {
cy.visit("http://localhost:3000/login");
cy.get("input[name=email]").type("test@example.com");
cy.get("input[name=password]").type("12345678");
cy.get("button[type=submit]").click();
cy.contains("Dashboard");
});
});
- Supports multiple browsers
- Language-agnostic
- Slower setup compared to Cypress
- Used in enterprise testing
- Rendering
- Props
- State changes
- User interactions
- API responses (mocked)
Testing Button Click
test("button click increments count", () => {
render(<Counter />);
fireEvent.click(screen.getByText("Increment"));
expect(screen.getByText("Count: 1")).toBeInTheDocument();
});
Mocking API Calls
jest.spyOn(global, "fetch").mockResolvedValue({
json: async () => ({ users: [] }),
});
- Status codes
- Response body
- Authentication
- Error handling
- Database interactions
Example: POST Route Test
describe("POST /api/users", () => {
it("creates a new user", async () => {
const res = await request(app)
.post("/api/users")
.send({ name: "Alice", email: "alice@test.com" });
expect(res.status).toBe(201);
expect(res.body.success).toBe(true);
});
});
Use:
- In-memory MongoDB
- Jest mocks
- Test databases
npm install --save-dev mongodb-memory-server
Test coverage measures how much code is tested.
npm test -- --coverage
Coverage includes:
- Statements
- Branches
- Functions
- Lines
- Write tests early
- Keep tests isolated
- Use descriptive test names
- Mock external services
- Separate unit and integration tests
- Run tests in CI/CD
Tests should run automatically on:
- Pull requests
- Merges
- Deployments
Example:
- name: Run tests
run: npm test
- Testing implementation instead of behavior
- Not mocking APIs
- Relying on real databases
- Ignoring edge cases
- Skipping frontend tests
- Testing is essential for reliable MERN applications
- Jest and Mocha handle unit testing
- Supertest and Chai test Express APIs
- Cypress handles full user workflows
- React components and Express routes must be tested separately
- Automated testing improves confidence and deployment safety
End of lesson.