[2026] Node.js Testing: Jest, Mocha, and Supertest

[2026] Node.js Testing: Jest, Mocha, and Supertest

이 글의 핵심

Test Node.js apps with Jest matchers and mocks, async tests, Supertest for Express APIs, MongoDB memory server, integration tests, coverage thresholds, Mocha/Chai, and TDD patterns.

Introduction

Why test?

Testing verifies that code behaves as intended. Benefits:

  • Catch bugs early: Before production
  • Safer refactors: Regression safety net
  • Documentation: Examples of usage
  • Confidence: Ship changes with less fear
  • Maintenance: Saves time over the long run Kinds of tests:
  • Unit: Single functions or classes
  • Integration: Multiple modules together
  • E2E: Full system paths

1. Jest

Install

npm install --save-dev jest

package.json: 아래 코드는 json를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

First tests

아래 코드는 javascript를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// math.js
function add(a, b) {
    return a + b;
}
function subtract(a, b) {
    return a - b;
}
module.exports = { add, subtract };

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// math.test.js
const { add, subtract } = require('./math');
describe('Math', () => {
    test('add sums two numbers', () => {
        expect(add(2, 3)).toBe(5);
        expect(add(-1, 1)).toBe(0);
        expect(add(0, 0)).toBe(0);
    });
    
    test('subtract subtracts two numbers', () => {
        expect(subtract(5, 3)).toBe(2);
        expect(subtract(1, 1)).toBe(0);
        expect(subtract(0, 5)).toBe(-5);
    });
});
npm test

Matchers

다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

describe('Matcher examples', () => {
    test('equality', () => {
        expect(2 + 2).toBe(4);
        expect({ name: 'Alice' }).toEqual({ name: 'Alice' });
    });
    
    test('truthiness', () => {
        expect(true).toBeTruthy();
        expect(false).toBeFalsy();
        expect(null).toBeNull();
        expect(undefined).toBeUndefined();
        expect('hello').toBeDefined();
    });
    
    test('numbers', () => {
        expect(10).toBeGreaterThan(5);
        expect(10).toBeGreaterThanOrEqual(10);
        expect(5).toBeLessThan(10);
        expect(0.1 + 0.2).toBeCloseTo(0.3);
    });
    
    test('strings', () => {
        expect('hello world').toMatch(/world/);
        expect('hello').toContain('ell');
    });
    
    test('arrays and objects', () => {
        const arr = ['apple', 'banana', 'cherry'];
        expect(arr).toContain('banana');
        expect(arr).toHaveLength(3);
        const obj = { name: 'Alice', age: 25 };
        expect(obj).toHaveProperty('name');
        expect(obj).toHaveProperty('age', 25);
    });
    
    test('exceptions', () => {
        expect(() => {
            throw new Error('oops');
        }).toThrow('oops');
    });
});

2. Async tests

Promises

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// api.js
async function fetchUser(id) {
    const response = await fetch(`https://api.example.com/users/${id}`);
    return response.json();
}
module.exports = { fetchUser };

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// api.test.js
const { fetchUser } = require('./api');
describe('API', () => {
    test('fetches a user', async () => {
        const user = await fetchUser(1);
        expect(user).toHaveProperty('id', 1);
        expect(user).toHaveProperty('name');
    });
    
    test('missing user rejects', async () => {
        await expect(fetchUser(999)).rejects.toThrow();
    });
});

Setup and teardown

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

describe('Database tests', () => {
    beforeAll(async () => {
        await connectDatabase();
    });
    
    beforeEach(async () => {
        await clearDatabase();
    });
    
    afterEach(async () => {
        /* cleanup */
    });
    
    afterAll(async () => {
        await closeDatabase();
    });
    
    test('creates a user', async () => {
        const user = await User.create({ name: 'Alice' });
        expect(user).toHaveProperty('_id');
    });
});

3. Mocks and spies

Mock functions

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

describe('Mock functions', () => {
    test('tracks calls', () => {
        const mockFn = jest.fn();
        mockFn('hello');
        mockFn('world');
        expect(mockFn).toHaveBeenCalledTimes(2);
        expect(mockFn).toHaveBeenCalledWith('hello');
        expect(mockFn).toHaveBeenLastCalledWith('world');
    });
    
    test('return values', () => {
        const mockFn = jest.fn();
        mockFn.mockReturnValue(42);
        expect(mockFn()).toBe(42);
        mockFn.mockReturnValueOnce(1).mockReturnValueOnce(2).mockReturnValue(3);
        expect(mockFn()).toBe(1);
        expect(mockFn()).toBe(2);
        expect(mockFn()).toBe(3);
        expect(mockFn()).toBe(3);
    });
});

Module mocks

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// user.js
const axios = require('axios');
async function getUser(id) {
    const response = await axios.get(`https://api.example.com/users/${id}`);
    return response.data;
}
module.exports = { getUser };

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// user.test.js
const axios = require('axios');
const { getUser } = require('./user');
jest.mock('axios');
describe('getUser', () => {
    test('returns user data', async () => {
        const mockUser = { id: 1, name: 'Alice' };
        axios.get.mockResolvedValue({ data: mockUser });
        const user = await getUser(1);
        expect(axios.get).toHaveBeenCalledWith('https://api.example.com/users/1');
        expect(user).toEqual(mockUser);
    });
    
    test('propagates errors', async () => {
        axios.get.mockRejectedValue(new Error('Network Error'));
        await expect(getUser(1)).rejects.toThrow('Network Error');
    });
});

Spies

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

describe('Spies', () => {
    test('wraps a method', () => {
        const obj = { method: () => 'original' };
        const spy = jest.spyOn(obj, 'method');
        obj.method();
        expect(spy).toHaveBeenCalled();
        spy.mockRestore();
    });
});

4. API tests with Supertest

npm install --save-dev supertest

다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// app.js
const express = require('express');
const app = express();
app.use(express.json());
app.get('/api/users', (req, res) => {
    res.json({ users: [] });
});
app.post('/api/users', (req, res) => {
    const { name, email } = req.body;
    if (!name || !email) {
        return res.status(400).json({ error: 'Name and email are required' });
    }
    res.status(201).json({ id: 1, name, email });
});
module.exports = app;

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// app.test.js
const request = require('supertest');
const app = require('./app');
describe('API', () => {
    describe('GET /api/users', () => {
        test('returns users', async () => {
            const response = await request(app)
                .get('/api/users')
                .expect(200)
                .expect('Content-Type', /json/);
            expect(response.body).toHaveProperty('users');
            expect(Array.isArray(response.body.users)).toBe(true);
        });
    });
    
    describe('POST /api/users', () => {
        test('creates a user', async () => {
            const newUser = { name: 'Alice', email: 'alice@example.com' };
            const response = await request(app)
                .post('/api/users')
                .send(newUser)
                .expect(201)
                .expect('Content-Type', /json/);
            expect(response.body).toMatchObject(newUser);
            expect(response.body).toHaveProperty('id');
        });
        
        test('returns 400 when invalid', async () => {
            const response = await request(app)
                .post('/api/users')
                .send({ name: 'Alice' })
                .expect(400);
            expect(response.body).toHaveProperty('error');
        });
    });
});

5. Database tests

MongoDB setup

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// test/setup.js
const mongoose = require('mongoose');
const { MongoMemoryServer } = require('mongodb-memory-server');
let mongoServer;
beforeAll(async () => {
    mongoServer = await MongoMemoryServer.create();
    await mongoose.connect(mongoServer.getUri());
});
afterAll(async () => {
    await mongoose.disconnect();
    await mongoServer.stop();
});
afterEach(async () => {
    const collections = mongoose.connection.collections;
    for (const key in collections) {
        await collections[key].deleteMany();
    }
});

jest.config.js: 아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

module.exports = {
    testEnvironment: 'node',
    setupFilesAfterEnv: ['<rootDir>/test/setup.js'],
    coveragePathIgnorePatterns: ['/node_modules/']
};

Model tests

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// models/User.test.js
const User = require('../models/User');
describe('User model', () => {
    test('creates a user', async () => {
        const userData = {
            name: 'Alice',
            email: 'alice@example.com',
            password: 'password123'
        };
        const user = await User.create(userData);
        expect(user).toHaveProperty('_id');
        expect(user.name).toBe(userData.name);
        expect(user.email).toBe(userData.email);
    });
    
    test('duplicate email fails', async () => {
        await User.create({
            name: 'Alice',
            email: 'alice@example.com',
            password: 'password123'
        });
        await expect(User.create({
            name: 'Bob',
            email: 'alice@example.com',
            password: 'password456'
        })).rejects.toThrow();
    });
    
    test('required fields enforced', async () => {
        await expect(User.create({ name: 'Alice' })).rejects.toThrow();
    });
});

6. Integration tests

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// test/integration/users.test.js
const request = require('supertest');
const app = require('../../app');
const User = require('../../models/User');
describe('Users API integration', () => {
    describe('POST /api/users', () => {
        test('creates a user', async () => {
            const userData = {
                name: 'Alice',
                email: 'alice@example.com',
                password: 'password123'
            };
            const response = await request(app)
                .post('/api/users')
                .send(userData)
                .expect(201);
            expect(response.body).toHaveProperty('id');
            expect(response.body.name).toBe(userData.name);
            const user = await User.findById(response.body.id);
            expect(user).toBeTruthy();
            expect(user.email).toBe(userData.email);
        });
    });
    
    describe('GET /api/users/:id', () => {
        test('returns a user', async () => {
            const user = await User.create({
                name: 'Alice',
                email: 'alice@example.com',
                password: 'password123'
            });
            const response = await request(app)
                .get(`/api/users/${user._id}`)
                .expect(200);
            expect(response.body.name).toBe(user.name);
            expect(response.body.email).toBe(user.email);
        });
        
        test('404 when missing', async () => {
            const response = await request(app)
                .get('/api/users/507f1f77bcf86cd799439011')
                .expect(404);
            expect(response.body).toHaveProperty('error');
        });
    });
});

Auth integration

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const request = require('supertest');
const app = require('../../app');
const User = require('../../models/User');
const bcrypt = require('bcrypt');
describe('Auth API', () => {
    describe('POST /auth/login', () => {
        test('logs in with valid credentials', async () => {
            const password = 'password123';
            await User.create({
                name: 'Alice',
                email: 'alice@example.com',
                password: await bcrypt.hash(password, 10)
            });
            const response = await request(app)
                .post('/auth/login')
                .send({ email: 'alice@example.com', password })
                .expect(200);
            expect(response.body).toHaveProperty('token');
            expect(response.body.user.email).toBe('alice@example.com');
        });
        
        test('401 on wrong password', async () => {
            await User.create({
                name: 'Alice',
                email: 'alice@example.com',
                password: await bcrypt.hash('password123', 10)
            });
            await request(app)
                .post('/auth/login')
                .send({ email: 'alice@example.com', password: 'wrong' })
                .expect(401);
        });
    });
    
    describe('GET /api/profile', () => {
        test('returns profile with token', async () => {
            await User.create({
                name: 'Alice',
                email: 'alice@example.com',
                password: await bcrypt.hash('password123', 10)
            });
            const loginResponse = await request(app)
                .post('/auth/login')
                .send({ email: 'alice@example.com', password: 'password123' });
            const token = loginResponse.body.token;
            const response = await request(app)
                .get('/api/profile')
                .set('Authorization', `Bearer ${token}`)
                .expect(200);
            expect(response.body.user.email).toBe('alice@example.com');
        });
        
        test('401 without token', async () => {
            await request(app).get('/api/profile').expect(401);
        });
    });
});

7. Mocha + Chai

npm install --save-dev mocha chai
{ "scripts": { "test": "mocha" } }

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 변수 선언 및 초기화
const { expect } = require('chai');
const { add, subtract } = require('../math');
describe('Math', () => {
    describe('add()', () => {
        it('adds two numbers', () => {
            expect(add(2, 3)).to.equal(5);
        });
    });
    describe('subtract()', () => {
        it('subtracts two numbers', () => {
            expect(subtract(5, 3)).to.equal(2);
        });
    });
});

Chai

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const { expect } = require('chai');
describe('Chai', () => {
    it('equality', () => {
        expect(2 + 2).to.equal(4);
        expect({ name: 'Alice' }).to.deep.equal({ name: 'Alice' });
    });
    it('types', () => {
        expect('hello').to.be.a('string');
        expect(123).to.be.a('number');
        expect([]).to.be.an('array');
    });
});

8. Coverage

npm test -- --coverage

jest.config.js: 다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

module.exports = {
    collectCoverageFrom: [
        'src/**/*.js',
        '!src/**/*.test.js',
        '!src/index.js'
    ],
    coverageThreshold: {
        global: {
            branches: 80,
            functions: 80,
            lines: 80,
            statements: 80
        }
    }
};

9. Practical examples

User service

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// services/userService.js
const User = require('../models/User');
const bcrypt = require('bcrypt');
class UserService {
    async createUser(userData) {
        const { email, password, name } = userData;
        const existing = await User.findOne({ email });
        if (existing) {
            throw new Error('Email already registered');
        }
        const hashedPassword = await bcrypt.hash(password, 10);
        return User.create({ email, password: hashedPassword, name });
    }
    
    async getUserById(id) {
        const user = await User.findById(id).select('-password');
        if (!user) throw new Error('User not found');
        return user;
    }
    
    async updateUser(id, updates) {
        const user = await User.findByIdAndUpdate(id, updates, {
            new: true,
            runValidators: true
        }).select('-password');
        if (!user) throw new Error('User not found');
        return user;
    }
    
    async deleteUser(id) {
        const user = await User.findByIdAndDelete(id);
        if (!user) throw new Error('User not found');
        return user;
    }
}
module.exports = new UserService();

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const userService = require('./userService');
const User = require('../models/User');
describe('UserService', () => {
    describe('createUser', () => {
        test('creates user', async () => {
            const userData = {
                name: 'Alice',
                email: 'alice@example.com',
                password: 'password123'
            };
            const user = await userService.createUser(userData);
            expect(user).toHaveProperty('_id');
            expect(user.password).not.toBe(userData.password);
        });
        
        test('duplicate email', async () => {
            await userService.createUser({
                name: 'Alice',
                email: 'alice@example.com',
                password: 'password123'
            });
            await expect(userService.createUser({
                name: 'Bob',
                email: 'alice@example.com',
                password: 'password456'
            })).rejects.toThrow('Email already registered');
        });
    });
});

Auth API (extended)

See integration example above; add JWT verification checks that match your auth middleware.

10. Test doubles

Mock (email)

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

jest.mock('nodemailer');
describe('sendEmail', () => {
    test('sends mail', async () => {
        const mockSendMail = jest.fn().mockResolvedValue({ messageId: '123' });
        nodemailer.createTransport.mockReturnValue({ sendMail: mockSendMail });
        await sendEmail('test@example.com', 'Subject', 'Body');
        expect(mockSendMail).toHaveBeenCalledWith({
            to: 'test@example.com',
            subject: 'Subject',
            text: 'Body'
        });
    });
});

Stub (payment)

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

jest.mock('axios');
describe('processPayment', () => {
    test('returns gateway data', async () => {
        axios.post.mockResolvedValue({
            data: { success: true, transactionId: 'txn_123' }
        });
        const result = await processPayment(10000);
        expect(result.success).toBe(true);
        expect(result.transactionId).toBe('txn_123');
    });
});

11. Common issues

Async timeout

Timeout - Async callback was not invoked within the 5000 ms timeout

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

test('slow op', async () => {
    await slowOperation();
}, 10000);
// jest.config.js
module.exports = { testTimeout: 10000 };

Shared state

Use beforeEach to reset fixtures; avoid module-level mutable arrays unless cleared.

DB cleanup

다음은 간단한 javascript 코드 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

afterEach(async () => {
    await User.deleteMany({});
    await Post.deleteMany({});
});

12. Practical tips

Layout

아래 코드는 code를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

test/
├── unit/
├── integration/
├── e2e/
└── setup.js

Helpers

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

async function createTestUser(overrides = {}) {
    const defaults = {
        name: 'Test User',
        email: 'test@example.com',
        password: await bcrypt.hash('password123', 10),
        role: 'user'
    };
    return User.create({ ...defaults, ...overrides });
}

TDD sketch

Write a failing test, minimal pass, refactor, add edge-case tests.

Summary

Takeaways

  1. Jest: Default all-in-one choice
  2. Mocha + Chai: Flexible stack
  3. Supertest: HTTP-level API tests
  4. Mocks: Isolate I/O and time
  5. Coverage: Enforce thresholds thoughtfully
  6. TDD: Red–green–refactor when it helps

Framework comparison

FrameworkProsCons
JestBatteries included, snapshotsHeavier
MochaMinimal coreNeeds plugins
AVAParallel by defaultSmaller ecosystem

Practices

  • Independent tests
  • One main assertion per test (rule of thumb)
  • Descriptive names
  • Arrange–Act–Assert
  • Test failure paths
  • Mock external systems

Next steps

Resources


... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3