Testing and Debugging

Bob wrote a RESTful API for an e-commerce platform, and every time he added a new feature, something else broke. Manual testing with Postman took 30 minutes per deployment, and he still missed edge cases. He introduced Jest for unit testing and Supertest for API integration testing — now npm test runs 50 tests in 3 seconds, and every deployment is backed by an automated CI check. He also added winston for structured logging, so when a bug slips through, the logs tell him exactly what went wrong.

You will learn:


1. Testing Overview

(1) Why Write Tests

Without Tests With Tests
Manual verification, 30 min each run Automated, 3 seconds
Change one thing, break everything Change one thing, tests tell you what broke
Anxiety before deployment Green lights, deploy with confidence
Afraid to refactor Refactor with test safety net

▶ Example: (2) Test Pyramid

100%
graph TD
    A["Unit Tests<br/>Many / Fast / Cheap"] --> B["Integration Tests<br/>Moderate / Slower"]
    B --> C["E2E Tests<br/>Few / Slow / Expensive"]
    style A fill:#4CAF50,color:#fff
    style B fill:#FF9800,color:#fff
    style C fill:#F44336,color:#fff

(3) Node.js Testing Tool Comparison

Tool Type Features Best For
Jest Unit + Integration Zero config, built-in mock/coverage, snapshot testing General purpose
Mocha Unit + Integration Flexible, rich plugins, needs chai/sinon Custom needs
Vitest Unit Vite ecosystem, native ESM, very fast Vite projects
node:test Unit Node.js built-in, zero dependencies Simple projects
Supertest HTTP testing HTTP request simulation, test Express routes API integration tests
Playwright E2E Browser automation, multi-browser support Frontend E2E

This lesson uses the Jest + Supertest combination, the most mainstream testing solution in the Node.js community.



2. Jest Unit Testing

▶ Example: (1) Installation & Setup

BASH
npm install --save-dev jest

Add the test script to package.json:

JSON
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  }
}

Jest works with zero configuration by default, automatically finding *.test.js or *.spec.js files.

▶ Example: (2) Basic Syntax: describe / it / expect

JAVASCRIPT
// math.js — the module to test
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

function multiply(a, b) {
  return a * b;
}

function divide(a, b) {
  if (b === 0) throw new Error('Division by zero');
  return a / b;
}

module.exports = { add, subtract, multiply, divide };
▶ Try it Yourself
JAVASCRIPT
// math.test.js
const { add, subtract, multiply, divide } = require('./math');

describe('Math utilities', () => {
  test('add should sum two numbers', () => {
    expect(add(2, 3)).toBe(5);
    expect(add(-1, 1)).toBe(0);
  });

  test('subtract should return the difference', () => {
    expect(subtract(5, 3)).toBe(2);
  });

  test('multiply should return the product', () => {
    expect(multiply(3, 4)).toBe(12);
  });

  test('divide should throw on zero divisor', () => {
    expect(() => divide(10, 0)).toThrow('Division by zero');
  });
});

Run:

BASH
npm test

Output:

TEXT
 PASS  ./math.test.js
  Math utilities
    ✓ add should sum two numbers (2 ms)
    ✓ subtract should return the difference
    ✓ multiply should return the product
    ✓ divide should throw on zero divisor (1 ms)

Tests:       4 passed, 4 total
Time:        0.5s

(3) Common Matchers

Matcher Meaning Example
toBe Strict equality (===) expect(1 + 1).toBe(2)
toEqual Deep equality expect(obj).toEqual({ a: 1 })
toBeTruthy / toBeFalsy Boolean check expect(flag).toBeTruthy()
toBeNull / toBeUndefined null / undefined expect(val).toBeNull()
toThrow Throws exception expect(fn).toThrow()
toContain Array contains expect([1, 2, 3]).toContain(2)
toMatch Regex match expect('hello').toMatch(/ell/)
toHaveLength Length check expect('abc').toHaveLength(3)
resolves / rejects Promise result expect(promise).resolves.toBe(5)
toHaveBeenCalled Mock called expect(fn).toHaveBeenCalled()

▶ Example: (4) Async Testing

JAVASCRIPT
// async-functions.js
function fetchUser(id) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id, name: `User_${id}` });
    }, 100);
  });
}

async function getUserEmail(id) {
  const user = await fetchUser(id);
  return `${user.name.toLowerCase()}@example.com`;
}

module.exports = { fetchUser, getUserEmail };
▶ Try it Yourself
JAVASCRIPT
// async-functions.test.js
const { fetchUser, getUserEmail } = require('./async-functions');

describe('Async function tests', () => {
  test('fetchUser returns user object', async () => {
    const user = await fetchUser(1);
    expect(user).toEqual({ id: 1, name: 'User_1' });
  });

  test('getUserEmail returns formatted email', async () => {
    const email = await getUserEmail(42);
    expect(email).toBe('user_42@example.com');
  });

  test('fetchUser resolves within 200ms', async () => {
    await expect(fetchUser(1)).resolves.toHaveProperty('id', 1);
  });
});

(5) Mocking

Mocks isolate external dependencies (databases, HTTP requests, filesystem) so tests only focus on the current module's logic.

JAVASCRIPT
// email-service.js
class EmailService {
  send(to, subject, body) {
    // actual SMTP call — too slow for unit tests
    console.log(`Sending email to ${to}: ${subject}`);
    return true;
  }
}

class UserService {
  constructor(emailService) {
    this.emailService = emailService;
  }

  register(name, email) {
    // business logic: validate + send welcome email
    if (!email.includes('@')) throw new Error('Invalid email');
    this.emailService.send(email, 'Welcome', `Hello ${name}!`);
    return { name, email, status: 'registered' };
  }
}

module.exports = { EmailService, UserService };
JAVASCRIPT
// email-service.test.js
const { EmailService, UserService } = require('./email-service');

describe('UserService', () => {
  let mockEmailService;
  let userService;

  beforeEach(() => {
    mockEmailService = {
      send: jest.fn().mockReturnValue(true),
    };
    userService = new UserService(mockEmailService);
  });

  test('register should call emailService.send', () => {
    const result = userService.register('Alice', 'alice@example.com');

    expect(result).toEqual({
      name: 'Alice',
      email: 'alice@example.com',
      status: 'registered',
    });

    expect(mockEmailService.send).toHaveBeenCalledWith(
      'alice@example.com',
      'Welcome',
      'Hello Alice!'
    );
  });

  test('register should throw on invalid email', () => {
    expect(() => userService.register('Bob', 'invalid')).toThrow('Invalid email');
    expect(mockEmailService.send).not.toHaveBeenCalled();
  });

  test('register should send exactly one email', () => {
    userService.register('Charlie', 'charlie@example.com');
    expect(mockEmailService.send).toHaveBeenCalledTimes(1);
  });
});

▶ Example: Basic Assertions with Jest

JAVASCRIPT
test('basic matchers', () => {
  expect(2 + 2).toBe(4);
  expect({ name: 'Alice' }).toEqual({ name: 'Alice' });
  expect([1, 2, 3]).toContain(2);
  expect('hello world').toMatch(/world/);
  expect(() => { throw new Error('fail'); }).toThrow('fail');
});
▶ Try it Yourself

Shows the most commonly used Jest matchers: toBe for primitives, toEqual for objects, toContain for arrays, toMatch for strings, and toThrow for error testing.



3. Supertest API Integration Testing

▶ Example: (1) Installation & Principle

BASH
npm install --save-dev supertest

Supertest starts an Express application in memory, sends HTTP requests, and asserts on responses — no real port listening needed.

100%
flowchart LR
    A["Test File"] -->|"request(app)"| B["Express App<br/>(in-memory)"]
    B -->|"response"| A
    A -->|"assert"| C["Jest expect"]

▶ Example: (2) Testing Express API

JAVASCRIPT
// app.js — the Express app (export without listen)
const express = require('express');
const app = express();

app.use(express.json());

let products = [
  { id: 1, name: 'Laptop', price: 999.99 },
  { id: 2, name: 'Phone', price: 499.99 },
];

app.get('/api/products', (req, res) => {
  res.json(products);
});

app.get('/api/products/:id', (req, res) => {
  const product = products.find(p => p.id === parseInt(req.params.id));
  if (!product) return res.status(404).json({ error: 'Product not found' });
  res.json(product);
});

app.post('/api/products', (req, res) => {
  const { name, price } = req.body;
  if (!name || price == null) {
    return res.status(400).json({ error: 'Name and price are required' });
  }
  const newProduct = { id: products.length + 1, name, price };
  products.push(newProduct);
  res.status(201).json(newProduct);
});

module.exports = app;
▶ Try it Yourself

Note: app.js only exports app, it does not call app.listen() inside the module. listen goes in server.js, so Supertest can test the app object directly.

JAVASCRIPT
// app.test.js
const request = require('supertest');
const app = require('./app');

describe('Products API', () => {
  test('GET /api/products should return product list', async () => {
    const response = await request(app).get('/api/products');

    expect(response.status).toBe(200);
    expect(response.body).toHaveLength(2);
    expect(response.body[0]).toHaveProperty('name', 'Laptop');
  });

  test('GET /api/products/:id should return a single product', async () => {
    const response = await request(app).get('/api/products/1');

    expect(response.status).toBe(200);
    expect(response.body.id).toBe(1);
  });

  test('GET /api/products/:id should return 404 for missing product', async () => {
    const response = await request(app).get('/api/products/999');

    expect(response.status).toBe(404);
    expect(response.body).toHaveProperty('error', 'Product not found');
  });

  test('POST /api/products should create a new product', async () => {
    const response = await request(app)
      .post('/api/products')
      .send({ name: 'Tablet', price: 299.99 });

    expect(response.status).toBe(201);
    expect(response.body).toHaveProperty('name', 'Tablet');
    expect(response.body).toHaveProperty('id', 3);
  });

  test('POST /api/products should return 400 for missing fields', async () => {
    const response = await request(app)
      .post('/api/products')
      .send({ name: 'Incomplete Product' });

    expect(response.status).toBe(400);
    expect(response.body).toHaveProperty('error');
  });
});

▶ Example: (3) Testing Authenticated API

JAVASCRIPT
// protected-routes.test.js
const request = require('supertest');
const app = require('./app');

describe('Protected API routes', () => {
  let token;

  beforeAll(async () => {
    // login to get JWT token
    const response = await request(app)
      .post('/api/auth/login')
      .send({ username: 'alice', password: 'secret123' });
    token = response.body.token;
  });

  test('should access protected route with valid token', async () => {
    const response = await request(app)
      .get('/api/profile')
      .set('Authorization', `Bearer ${token}`);

    expect(response.status).toBe(200);
    expect(response.body).toHaveProperty('username', 'alice');
  });

  test('should reject access without token', async () => {
    const response = await request(app).get('/api/profile');

    expect(response.status).toBe(401);
  });

  test('should reject access with invalid token', async () => {
    const response = await request(app)
      .get('/api/profile')
      .set('Authorization', 'Bearer invalid_token_here');

    expect(response.status).toBe(401);
  });
});
▶ Try it Yourself

▶ Example: API Health Check Test

JAVASCRIPT
const request = require('supertest');
const express = require('express');

const app = express();
app.get('/health', (req, res) => res.json({ status: 'ok' }));

describe('GET /health', () => {
  it('returns 200 with status ok', async () => {
    const res = await request(app).get('/health');
    expect(res.status).toBe(200);
    expect(res.body.status).toBe('ok');
  });
});
▶ Try it Yourself

A minimal API health check test using Supertest — creates a basic Express app, sends a GET request, and asserts the JSON response body and status code.



4. Node.js Debugging

(1) node inspect + Chrome DevTools

Node.js has a built-in debugger — no extra tools required.

BASH
node inspect app.js

Then open chrome://inspect in Chrome and click "Open dedicated DevTools for Node".

Common debugging operations:

Action DevTools Shortcut Command Line
Continue F8 c
Step over F10 n
Step into F11 s
Step out Shift+F11 o
Set breakpoint Click line number setBreakpoint()

▶ Example: (2) Setting Breakpoints in Code

JAVASCRIPT
// debug-demo.js
function calculateDiscount(price, memberLevel) {
  debugger; // execution pauses here in inspector
  let discount = 0;
  if (memberLevel === 'gold') discount = 0.2;
  else if (memberLevel === 'silver') discount = 0.1;
  const finalPrice = price * (1 - discount);
  return finalPrice;
}

console.log(calculateDiscount(100, 'gold'));
console.log(calculateDiscount(100, 'silver'));
console.log(calculateDiscount(100, 'unknown'));
▶ Try it Yourself
BASH
node inspect debug-demo.js

▶ Example: (3) console Debugging Tips

JAVASCRIPT
// console debugging methods
const users = [
  { id: 1, name: 'Alice', role: 'admin' },
  { id: 2, name: 'Bob', role: 'user' },
  { id: 3, name: 'Charlie', role: 'admin' },
];

// Table display
console.table(users);

// Measure execution time
console.time('database-query');
// ... query logic ...
console.timeEnd('database-query');

// Stack trace
console.trace('Where is this called from?');

// Grouped output
console.group('User Details');
console.log('Name: Alice');
console.log('Role: admin');
console.groupEnd();
▶ Try it Yourself

5. winston Structured Logging

(1) Why winston Instead of console.log

console.log winston
No log levels Supports debug/info/warn/error
No file output Supports Console + File + HTTP and other transports
No formatting Supports JSON / timestamp / custom formats
No log rotation Pair with winston-daily-rotate-file for auto rotation
Not recommended for production Production-grade choice

▶ Example: (2) Installation & Basic Setup

BASH
npm install winston
npm install winston-daily-rotate-file
JAVASCRIPT
// logger.js
const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: 'my-app' },
  transports: [
    // Error logs — separate file
    new DailyRotateFile({
      filename: 'logs/error-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      level: 'error',
      maxSize: '20m',
      maxFiles: '14d',
    }),
    // All logs — combined file
    new DailyRotateFile({
      filename: 'logs/combined-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '14d',
    }),
  ],
});

// Development: also log to console with colorized output
if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  }));
}

module.exports = logger;

▶ Example: (3) Using in an Express Application

JAVASCRIPT
// app-with-logger.js
const express = require('express');
const logger = require('./logger');

const app = express();
app.use(express.json());

// Request logging middleware
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - start;
    logger.info('HTTP request', {
      method: req.method,
      url: req.url,
      status: res.statusCode,
      duration: `${duration}ms`,
    });
  });
  next();
});

app.get('/api/health', (req, res) => {
  logger.debug('Health check requested');
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

app.get('/api/users/:id', (req, res) => {
  const userId = req.params.id;
  logger.info('User lookup', { userId });

  if (isNaN(userId)) {
    logger.warn('Invalid user ID format', { userId });
    return res.status(400).json({ error: 'Invalid user ID' });
  }

  // Simulate user lookup
  const user = { id: parseInt(userId), name: 'Alice' };
  logger.info('User found', { userId: user.id, name: user.name });
  res.json(user);
});

app.use((err, req, res, next) => {
  logger.error('Unhandled error', { error: err.message, stack: err.stack });
  res.status(500).json({ error: 'Internal server error' });
});

module.exports = app;
▶ Try it Yourself

Log file output example:

JSON
{"level":"info","message":"HTTP request","service":"my-app","timestamp":"2026-07-13 10:30:00","method":"GET","url":"/api/users/1","status":200,"duration":"15ms"}
{"level":"warn","message":"Invalid user ID format","service":"my-app","timestamp":"2026-07-13 10:30:05","userId":"abc"}
{"level":"error","message":"Unhandled error","service":"my-app","timestamp":"2026-07-13 10:31:00","error":"Cannot read property 'name' of undefined","stack":"TypeError: Cannot read property..."}

▶ Example: Structured Logging with winston

JAVASCRIPT
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [new winston.transports.Console()],
});

logger.info('Server started', { port: 3000, env: 'development' });
logger.warn('Memory usage high', { used: '850MB' });
logger.error('Database connection failed', { db: 'main', retry: 3 });
▶ Try it Yourself

Demonstrates structured JSON logging with winston — different log levels (info, warn, error) and contextual metadata attached to each log entry.



6. Test Coverage & TDD

(1) Test Coverage

Jest includes Istanbul for coverage reporting:

BASH
npm run test:coverage

Output:

TEXT
----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files |    90.5 |    85.71 |     100 |    90.5 |
 math.js  |     100 |      100 |     100 |     100 |
 app.js   |   88.24 |    83.33 |     100 |   88.24 | 45-48
----------|---------|----------|---------|---------|-------------------

Coverage dimensions explained:

Dimension Meaning Suggested Target
Statements Percentage of statements executed ≥ 80%
Branches Percentage of condition branches executed ≥ 75%
Functions Percentage of functions called ≥ 85%
Lines Percentage of code lines executed ≥ 80%

Chasing 100% coverage has diminishing returns; 80% coverage catches most bugs.

▶ Example: (2) TDD Workflow (Red-Green-Refactor)

100%
flowchart LR
    A["🔴 Red<br/>Write failing test"] --> B["🟢 Green<br/>Write minimal code to pass"]
    B --> C["🔵 Refactor<br/>Improve code while tests pass"]
    C --> A
  1. Red: Write a test first (it will fail because the feature doesn't exist yet)
  2. Green: Write the minimum code to make the test pass
  3. Refactor: Improve the code while tests still pass

▶ Example: TDD String Utility Functions

JAVASCRIPT
// string-utils.test.js — write test FIRST (Red)
const { capitalize, truncate, slugify } = require('./string-utils');

describe('StringUtils', () => {
  describe('capitalize', () => {
    test('should capitalize first letter', () => {
      expect(capitalize('hello')).toBe('Hello');
    });

    test('should handle empty string', () => {
      expect(capitalize('')).toBe('');
    });

    test('should handle already capitalized', () => {
      expect(capitalize('Hello')).toBe('Hello');
    });
  });

  describe('truncate', () => {
    test('should truncate long strings', () => {
      expect(truncate('Hello World', 5)).toBe('Hello...');
    });

    test('should not truncate short strings', () => {
      expect(truncate('Hi', 10)).toBe('Hi');
    });
  });

  describe('slugify', () => {
    test('should convert to URL slug', () => {
      expect(slugify('Hello World')).toBe('hello-world');
    });

    test('should handle special characters', () => {
      expect(slugify('C# & .NET!')).toBe('c-and-net');
    });
  });
});
▶ Try it Yourself
JAVASCRIPT
// string-utils.js — write implementation AFTER test (Green)
function capitalize(str) {
  if (!str) return '';
  return str.charAt(0).toUpperCase() + str.slice(1);
}

function truncate(str, maxLength) {
  if (str.length <= maxLength) return str;
  return str.slice(0, maxLength) + '...';
}

function slugify(str) {
  return str
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '');
}

module.exports = { capitalize, truncate, slugify };


7. Comprehensive Example: Writing a Complete Test Suite for a Task Management API

▶ Example: Tasks API Test Suite

Step 1 — Application Code

JAVASCRIPT
// tasks-app.js
const express = require('express');
const app = express();
app.use(express.json());

let tasks = [];
let nextId = 1;

function resetTasks() {
  tasks = [];
  nextId = 1;
}

app.get('/api/tasks', (req, res) => {
  const { status, priority } = req.query;
  let filtered = tasks;
  if (status) filtered = filtered.filter(t => t.status === status);
  if (priority) filtered = filtered.filter(t => t.priority === priority);
  res.json(filtered);
});

app.get('/api/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) return res.status(404).json({ error: 'Task not found' });
  res.json(task);
});

app.post('/api/tasks', (req, res) => {
  const { title, priority = 'medium' } = req.body;
  if (!title) return res.status(400).json({ error: 'Title is required' });
  const task = { id: nextId++, title, priority, status: 'pending' };
  tasks.push(task);
  res.status(201).json(task);
});

app.patch('/api/tasks/:id', (req, res) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (!task) return res.status(404).json({ error: 'Task not found' });
  const { title, status, priority } = req.body;
  if (title) task.title = title;
  if (status) task.status = status;
  if (priority) task.priority = priority;
  res.json(task);
});

app.delete('/api/tasks/:id', (req, res) => {
  const index = tasks.findIndex(t => t.id === parseInt(req.params.id));
  if (index === -1) return res.status(404).json({ error: 'Task not found' });
  const deleted = tasks.splice(index, 1);
  res.json(deleted[0]);
});

module.exports = { app, resetTasks };
▶ Try it Yourself

Step 2 — Complete Test Suite

JAVASCRIPT
// tasks-app.test.js
const request = require('supertest');
const { app, resetTasks } = require('./tasks-app');

beforeEach(() => {
  resetTasks();
});

describe('Tasks API — CRUD operations', () => {
  test('should create a task', async () => {
    const response = await request(app)
      .post('/api/tasks')
      .send({ title: 'Write tests', priority: 'high' });

    expect(response.status).toBe(201);
    expect(response.body).toMatchObject({
      id: 1,
      title: 'Write tests',
      priority: 'high',
      status: 'pending',
    });
  });

  test('should reject task without title', async () => {
    const response = await request(app)
      .post('/api/tasks')
      .send({ priority: 'high' });

    expect(response.status).toBe(400);
    expect(response.body.error).toBe('Title is required');
  });

  test('should list all tasks', async () => {
    await request(app).post('/api/tasks').send({ title: 'Task 1' });
    await request(app).post('/api/tasks').send({ title: 'Task 2' });

    const response = await request(app).get('/api/tasks');
    expect(response.status).toBe(200);
    expect(response.body).toHaveLength(2);
  });

  test('should filter tasks by status', async () => {
    await request(app).post('/api/tasks').send({ title: 'Pending task' });

    const createRes = await request(app).post('/api/tasks').send({ title: 'Done task' });
    await request(app).patch(`/api/tasks/${createRes.body.id}`).send({ status: 'done' });

    const response = await request(app).get('/api/tasks?status=done');
    expect(response.body).toHaveLength(1);
    expect(response.body[0].status).toBe('done');
  });

  test('should update a task', async () => {
    const createRes = await request(app).post('/api/tasks').send({ title: 'Old title' });

    const response = await request(app)
      .patch(`/api/tasks/${createRes.body.id}`)
      .send({ title: 'New title', status: 'done' });

    expect(response.status).toBe(200);
    expect(response.body.title).toBe('New title');
    expect(response.body.status).toBe('done');
  });

  test('should delete a task', async () => {
    const createRes = await request(app).post('/api/tasks').send({ title: 'To delete' });

    const response = await request(app).delete(`/api/tasks/${createRes.body.id}`);
    expect(response.status).toBe(200);

    const listRes = await request(app).get('/api/tasks');
    expect(listRes.body).toHaveLength(0);
  });

  test('should return 404 for non-existent task', async () => {
    const response = await request(app).get('/api/tasks/999');
    expect(response.status).toBe(404);
  });
});

Run:

BASH
npm test

Output:

TEXT
 PASS  ./tasks-app.test.js
  Tasks API — CRUD operations
    ✓ should create a task (15 ms)
    ✓ should reject task without title (3 ms)
    ✓ should list all tasks (4 ms)
    ✓ should filter tasks by status (8 ms)
    ✓ should update a task (5 ms)
    ✓ should delete a task (5 ms)
    ✓ should return 404 for non-existent task (2 ms)

Tests:       7 passed, 7 total
Time:        0.8s

❓ FAQ

Q Should I pick Jest or Mocha?
A Jest is zero-config with built-in mock/coverage, great for most projects; Mocha is flexible but needs chai/sinon/istanbul, best for projects requiring deep customization. Jest is recommended for new projects.
Q What's the difference between unit tests and integration tests?
A Unit tests isolate and test individual functions/modules using mocks to replace external dependencies — they are fast. Integration tests verify cooperation between multiple modules (e.g., API + database) using real components — they are slower but closer to real scenarios.
Q Do tests need a real database connection?
A Unit tests should never use a real database — use mocks. Integration tests can use an in-memory database (e.g., SQLite :memory:) or a dedicated test database instance. E2E tests use a test environment database.
Q What are the winston log levels?
A From lowest to highest: error → warn → info → http → verbose → debug → silly. When level is set to info, only info and above are output.
Q How to debug Jest tests themselves?
A Run node --inspect-brk node_modules/.bin/jest --runInBand, then debug in Chrome DevTools. --runInBand ensures Jest runs on a single thread so breakpoints work.
Q Does Supertest need a real server to start?
A No. Supertest creates an in-memory server via http.createServer(app) — no port is occupied, and it closes automatically when tests finish.

📖 Summary


📝 Exercises

  1. Write Jest unit tests for the following function: function isPalindrome(str) { return str === str.split('').reverse().join(''); }, covering normal palindromes, non-palindromes, empty strings, and mixed case scenarios
  2. Create an Express API (CRUD for /api/books), write at least 5 integration tests using Supertest (create, read, update, delete, 404)
  3. Add winston logging middleware to an existing Express app, logging method/url/status/duration for every request, with daily log rotation
  4. Use TDD to develop a formatCurrency(amount, currency) function: first write a failing test (e.g., formatCurrency(1234.5, 'USD')'$1,234.50'), then write the implementation
  5. Run jest --coverage, find files with coverage below 80%, and add test cases to meet the coverage target

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏