Testing in JavaScript: unit, integration, e2e from A to Z

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Testing in JavaScript: from unit to end-to-end tests

\n\n

Quality testing is an integral part of professional JavaScript development. In this article, we will cover three main levels of testing: unit, integration, and e2e tests. You will learn which tools to use, how to write tests in Node.js, and how to build an effective testing strategy.

\n\n

1. Unit Testing: the foundation of the testing pyramid

\n\n

Unit tests check isolated modules — functions, classes, or individual methods. The main rule: the code being tested should not depend on external resources (databases, APIs, file system).

\n\n

Tools for unit testing

\n
    \n
  • Jest — the most popular framework from Facebook. It includes mocks, snapshots, and code coverage out of the box.
  • \n
  • Mocha + Chai — a flexible combo for those who like to configure everything manually.
  • \n
  • Vitest — a modern alternative to Jest, optimized for Vite projects.
  • \n
\n\n
// Example unit test with Jest\nconst { calculateDiscount } = require('./pricing');\n\ndescribe('calculateDiscount', () => {\n  test('returns 10% discount for amounts > 1000', () => {\n    const result = calculateDiscount(1500);\n    expect(result).toBe(1350);\n  });\n\n  test('does not apply discount for amounts <= 1000', () => {\n    const result = calculateDiscount(500);\n    expect(result).toBe(500);\n  });\n\n  test('handles boundary value 1000', () => {\n    const result = calculateDiscount(1000);\n    expect(result).toBe(1000);\n  });\n});
\n\n

Best practices for unit testing

\n
    \n
  • Test one behavior at a time (one assert per test).
  • \n
  • Use descriptive test names (given/when/then).
  • \n
  • Avoid testing private methods — test the public interface.
  • \n
  • Maintain code coverage at 70-80%.
  • \n
\n\n

2. Integration Testing: checking component interaction

\n\n

Integration tests check how different modules work together. In the context of Node.js, this usually means testing with a real database, file system, or external APIs.

\n\n

Typical integration testing scenarios

\n
    \n
  • Interaction between the controller, service layer, and database.
  • \n
  • Testing middleware and Express/Koa routes.
  • \n
  • Testing repositories with a test database (SQLite in memory or PostgreSQL in Docker).
  • \n
\n\n
// Example integration test with Supertest and Jest\nconst request = require('supertest');\nconst app = require('../app');\nconst { createTestUser, cleanDatabase } = require('./helpers');\n\nbeforeEach(async () => {\n  await cleanDatabase();\n});\n\ndescribe('POST /api/users', () => {\n  test('creates a user and returns 201', async () => {\n    const response = await request(app)\n      .post('/api/users')\n      .send({ name: 'Ivan', email: 'ivan@test.com' });\n\n    expect(response.status).toBe(201);\n    expect(response.body).toHaveProperty('id');\n    expect(response.body.name).toBe('Ivan');\n  });\n\n  test('returns 400 on duplicate email', async () => {\n    await createTestUser({ email: 'ivan@test.com' });\n\n    const response = await request(app)\n      .post('/api/users')\n      .send({ name: 'Petr', email: 'ivan@test.com' });\n\n    expect(response.status).toBe(400);\n    expect(response.body.error).toBe('Email is already in use');\n  });\n});
\n\n

Setting up test infrastructure

\n
    \n
  • Docker Compose — for spinning up test services (DB, Redis).
  • \n
  • Testcontainers — programmatic container management from tests.
  • \n
  • In-memory databases (e.g., mongodb-memory-server) — for fast tests without external dependencies.
  • \n
\n\n

3. E2E Testing: checking the system as a whole

Blogs

Book Recommendations