| name | backend-testing |
| description | Write comprehensive backend tests including unit tests, integration tests, and API tests. Use when testing REST APIs, database operations, authentication flows, business logic, or setting up automated testing in CI/CD pipelines. Handles Jest, Pytest, Mocha, testing strategies, mocking, test coverage, and test automation workflows. |
| metadata | {"tags":"testing, backend, unit-test, integration-test, API-test, Jest, Pytest, TDD, CI/CD","platforms":"Claude, ChatGPT, Gemini"} |
Backend Testing
When to use this skill
์ด ์คํฌ์ ํธ๋ฆฌ๊ฑฐํด์ผ ํ๋ ๊ตฌ์ฒด์ ์ธ ์ํฉ์ ๋์ดํฉ๋๋ค:
- ์ ๊ธฐ๋ฅ ๊ฐ๋ฐ: TDD(Test-Driven Development) ๋ฐฉ์์ผ๋ก ํ
์คํธ ๋จผ์ ์์ฑ
- API ์๋ํฌ์ธํธ ์ถ๊ฐ: REST API์ ์ฑ๊ณต/์คํจ ์ผ์ด์ค ํ
์คํธ
- ๋ฒ๊ทธ ์์ : ํ๊ท ๋ฐฉ์ง๋ฅผ ์ํ ํ
์คํธ ์ถ๊ฐ
- ๋ฆฌํฉํ ๋ง ์ : ๊ธฐ์กด ๋์์ ๋ณด์ฅํ๋ ํ
์คํธ ์์ฑ
- CI/CD ์ค์ : ์๋ํ๋ ํ
์คํธ ํ์ดํ๋ผ์ธ ๊ตฌ์ถ
์
๋ ฅ ํ์ (Input Format)
์ฌ์ฉ์๋ก๋ถํฐ ๋ฐ์์ผ ํ ์
๋ ฅ์ ํ์๊ณผ ํ์/์ ํ ์ ๋ณด:
ํ์ ์ ๋ณด
- ํ๋ ์์ํฌ: Express, Django, FastAPI, Spring Boot ๋ฑ
- ํ
์คํธ ๋๊ตฌ: Jest, Pytest, Mocha/Chai, JUnit ๋ฑ
- ํ
์คํธ ๋์: API ์๋ํฌ์ธํธ, ๋น์ฆ๋์ค ๋ก์ง, DB ์์
๋ฑ
์ ํ ์ ๋ณด
- ๋ฐ์ดํฐ๋ฒ ์ด์ค: PostgreSQL, MySQL, MongoDB (๊ธฐ๋ณธ๊ฐ: in-memory DB)
- ๋ชจํน ๋ผ์ด๋ธ๋ฌ๋ฆฌ: jest.mock, sinon, unittest.mock (๊ธฐ๋ณธ๊ฐ: ํ๋ ์์ํฌ ๋ด์ฅ)
- ์ปค๋ฒ๋ฆฌ์ง ๋ชฉํ: 80%, 90% ๋ฑ (๊ธฐ๋ณธ๊ฐ: 80%)
- E2E ๋๊ตฌ: Supertest, TestClient, RestAssured (์ ํ)
์
๋ ฅ ์์
Express.js API์ ์ฌ์ฉ์ ์ธ์ฆ ์๋ํฌ์ธํธ๋ฅผ ํ
์คํธํด์ค:
- ํ๋ ์์ํฌ: Express + TypeScript
- ํ
์คํธ ๋๊ตฌ: Jest + Supertest
- ๋์: POST /auth/register, POST /auth/login
- DB: PostgreSQL (ํ
์คํธ์ฉ in-memory)
- ์ปค๋ฒ๋ฆฌ์ง: 90% ์ด์
Instructions
๋จ๊ณ๋ณ๋ก ์ ํํ๊ฒ ๋ฐ๋ผ์ผ ํ ์์
์์๋ฅผ ๋ช
์ํฉ๋๋ค.
Step 1: ํ
์คํธ ํ๊ฒฝ ์ค์
ํ
์คํธ ํ๋ ์์ํฌ ๋ฐ ๋๊ตฌ๋ฅผ ์ค์นํ๊ณ ์ค์ ํฉ๋๋ค.
์์
๋ด์ฉ:
- ํ
์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
- ํ
์คํธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ (in-memory ๋๋ ๋ณ๋ DB)
- ํ๊ฒฝ๋ณ์ ๋ถ๋ฆฌ (.env.test)
- jest.config.js ๋๋ pytest.ini ์ค์
์์ (Node.js + Jest + Supertest):
npm install --save-dev jest ts-jest @types/jest supertest @types/supertest
jest.config.js:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.test.ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/__tests__/**'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts']
};
setup.ts (ํ
์คํธ ์ ์ญ ์ค์ ):
import { db } from '../database';
beforeEach(async () => {
await db.migrate.latest();
await db.seed.run();
});
afterEach(async () => {
await db.migrate.rollback();
});
afterAll(async () => {
await db.destroy();
});
Step 2: Unit Test ์์ฑ (๋น์ฆ๋์ค ๋ก์ง)
๊ฐ๋ณ ํจ์/ํด์ค์ ๋จ์ ํ
์คํธ๋ฅผ ์์ฑํฉ๋๋ค.
์์
๋ด์ฉ:
- ์์ ํจ์ ํ
์คํธ (์์กด์ฑ ์์)
- ๋ชจํน์ ํตํ ์์กด์ฑ ๊ฒฉ๋ฆฌ
- Edge case ํ
์คํธ (๊ฒฝ๊ณ๊ฐ, ์์ธ)
- AAA ํจํด (Arrange-Act-Assert)
ํ๋จ ๊ธฐ์ค:
- ์ธ๋ถ ์์กด์ฑ(DB, API) ์์ โ ์์ Unit Test
- ์ธ๋ถ ์์กด์ฑ ์์ โ Mock/Stub ์ฌ์ฉ
- ๋ณต์กํ ๋ก์ง โ ๋ค์ํ ์
๋ ฅ ์ผ์ด์ค ํ
์คํธ
์์ (๋น๋ฐ๋ฒํธ ๊ฒ์ฆ ํจ์):
export function validatePassword(password: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain lowercase letter');
}
if (!/\d/.test(password)) {
errors.push('Password must contain number');
}
if (!/[!@#$%^&*]/.test(password)) {
errors.push('Password must contain special character');
}
return { valid: errors.length === 0, errors };
}
import { validatePassword } from '../../utils/password';
describe('validatePassword', () => {
it('should accept valid password', () => {
const result = validatePassword('Password123!');
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should reject password shorter than 8 characters', () => {
const result = validatePassword('Pass1!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must be at least 8 characters');
});
it('should reject password without uppercase', () => {
const result = validatePassword('password123!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain uppercase letter');
});
it('should reject password without lowercase', () => {
const result = validatePassword('PASSWORD123!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain lowercase letter');
});
it('should reject password without number', () => {
const result = validatePassword('Password!');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain number');
});
it('should reject password without special character', () => {
const result = validatePassword('Password123');
expect(result.valid).toBe(false);
expect(result.errors).toContain('Password must contain special character');
});
it('should return multiple errors for invalid password', () => {
const result = validatePassword('pass');
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThan(1);
});
});
Step 3: Integration Test (API ์๋ํฌ์ธํธ)
API ์๋ํฌ์ธํธ์ ํตํฉ ํ
์คํธ๋ฅผ ์์ฑํฉ๋๋ค.
์์
๋ด์ฉ:
- HTTP ์์ฒญ/์๋ต ํ
์คํธ
- ์ฑ๊ณต ์ผ์ด์ค (200, 201)
- ์คํจ ์ผ์ด์ค (400, 401, 404, 500)
- ์ธ์ฆ/๊ถํ ํ
์คํธ
- ์
๋ ฅ ๊ฒ์ฆ ํ
์คํธ
ํ์ธ ์ฌํญ:
์์ (Express.js + Supertest):
import request from 'supertest';
import app from '../../app';
import { db } from '../../database';
describe('POST /auth/register', () => {
it('should register new user successfully', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'testuser',
password: 'Password123!'
});
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('user');
expect(response.body).toHaveProperty('accessToken');
expect(response.body.user.email).toBe('test@example.com');
const user = await db.user.findUnique({ where: { email: 'test@example.com' } });
expect(user).toBeTruthy();
expect(user.username).toBe('testuser');
});
it('should reject duplicate email', async () => {
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'user1',
password: 'Password123!'
});
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'user2',
password: 'Password123!'
});
expect(response.status).toBe(409);
expect(response.body.error).toContain('already exists');
});
it('should reject weak password', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'testuser',
password: 'weak'
});
expect(response.status).toBe(400);
expect(response.body.error).toBeDefined();
});
it('should reject missing fields', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com'
});
expect(response.status).toBe(400);
});
});
describe('POST /auth/login', () => {
beforeEach(async () => {
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
username: 'testuser',
password: 'Password123!'
});
});
it('should login with valid credentials', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'Password123!'
});
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('accessToken');
expect(response.body).toHaveProperty('refreshToken');
expect(response.body.user.email).toBe('test@example.com');
});
it('should reject invalid password', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'WrongPassword123!'
});
expect(response.status).toBe(401);
expect(response.body.error).toContain('Invalid credentials');
});
it('should reject non-existent user', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'nonexistent@example.com',
password: 'Password123!'
});
expect(response.status).toBe(401);
});
});
Step 4: ์ธ์ฆ/๊ถํ ํ
์คํธ
JWT ํ ํฐ ๋ฐ ๊ถํ ๊ธฐ๋ฐ ์ ๊ทผ ์ ์ด๋ฅผ ํ
์คํธํฉ๋๋ค.
์์
๋ด์ฉ:
- ํ ํฐ ์์ด ์ ๊ทผ ์ 401 ํ์ธ
- ์ ํจํ ํ ํฐ์ผ๋ก ์ ๊ทผ ์ฑ๊ณต ํ์ธ
- ๋ง๋ฃ๋ ํ ํฐ ์ฒ๋ฆฌ ํ
์คํธ
- Role-based ๊ถํ ํ
์คํธ
์์:
describe('Protected Routes', () => {
let accessToken: string;
let adminToken: string;
beforeEach(async () => {
const userResponse = await request(app)
.post('/api/auth/register')
.send({
email: 'user@example.com',
username: 'user',
password: 'Password123!'
});
accessToken = userResponse.body.accessToken;
const adminResponse = await request(app)
.post('/api/auth/register')
.send({
email: 'admin@example.com',
username: 'admin',
password: 'Password123!'
});
await db.user.update({
where: { email: 'admin@example.com' },
data: { role: 'admin' }
});
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: 'admin@example.com',
password: 'Password123!'
});
adminToken = loginResponse.body.accessToken;
});
describe('GET /api/auth/me', () => {
it('should return current user with valid token', async () => {
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', `Bearer ${accessToken}`);
expect(response.status).toBe(200);
expect(response.body.user.email).toBe('user@example.com');
});
it('should reject request without token', async () => {
const response = await request(app)
.get('/api/auth/me');
expect(response.status).toBe(401);
});
it('should reject request with invalid token', async () => {
const response = await request(app)
.get('/api/auth/me')
.set('Authorization', 'Bearer invalid-token');
expect(response.status).toBe(403);
});
});
describe('DELETE /api/users/:id (Admin only)', () => {
it('should allow admin to delete user', async () => {
const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });
const response = await request(app)
.delete(`/api/users/${targetUser.id}`)
.set('Authorization', `Bearer ${adminToken}`);
expect(response.status).toBe(200);
});
it('should forbid non-admin from deleting user', async () => {
const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });
const response = await request(app)
.delete(`/api/users/${targetUser.id}`)
.set('Authorization', `Bearer ${accessToken}`);
expect(response.status).toBe(403);
});
});
});
Step 5: Mocking ๋ฐ ํ
์คํธ ๊ฒฉ๋ฆฌ
์ธ๋ถ ์์กด์ฑ์ ๋ชจํนํ์ฌ ํ
์คํธ๋ฅผ ๊ฒฉ๋ฆฌํฉ๋๋ค.
์์
๋ด์ฉ:
- ์ธ๋ถ API ๋ชจํน
- ์ด๋ฉ์ผ ๋ฐ์ก ๋ชจํน
- ํ์ผ ์์คํ
๋ชจํน
- ์๊ฐ ๊ด๋ จ ํจ์ ๋ชจํน
์์ (์ธ๋ถ API ๋ชจํน):
export async function sendVerificationEmail(email: string, token: string): Promise<void> {
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}` },
body: JSON.stringify({
to: email,
subject: 'Verify your email',
html: `<a href="https://example.com/verify?token=${token}">Verify</a>`
})
});
if (!response.ok) {
throw new Error('Failed to send email');
}
}
import { sendVerificationEmail } from '../../services/emailService';
global.fetch = jest.fn();
describe('sendVerificationEmail', () => {
beforeEach(() => {
(fetch as jest.Mock).mockClear();
});
it('should send email successfully', async () => {
(fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
status: 200
});
await expect(sendVerificationEmail('test@example.com', 'token123'))
.resolves
.toBeUndefined();
expect(fetch).toHaveBeenCalledWith(
'https://api.sendgrid.com/v3/mail/send',
expect.objectContaining({
method: 'POST'
})
);
});
it('should throw error if email sending fails', async () => {
(fetch as jest.Mock).mockResolvedValueOnce({
ok: false,
status: 500
});
await expect(sendVerificationEmail('test@example.com', 'token123'))
.rejects
.toThrow('Failed to send email');
});
});
Output format
๊ฒฐ๊ณผ๋ฌผ์ด ๋ฐ๋ผ์ผ ํ ์ ํํ ํ์์ ์ ์ํฉ๋๋ค.
๊ธฐ๋ณธ ๊ตฌ์กฐ
ํ๋ก์ ํธ/
โโโ src/
โ โโโ __tests__/
โ โ โโโ setup.ts # ํ
์คํธ ์ ์ญ ์ค์
โ โ โโโ utils/
โ โ โ โโโ password.test.ts # Unit tests
โ โ โโโ services/
โ โ โ โโโ emailService.test.ts
โ โ โโโ api/
โ โ โโโ auth.test.ts # Integration tests
โ โ โโโ users.test.ts
โ โโโ ...
โโโ jest.config.js
โโโ package.json
ํ
์คํธ ์คํ ์คํฌ๋ฆฝํธ (package.json)
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage --maxWorkers=2"
}
}
์ปค๋ฒ๋ฆฌ์ง ๋ฆฌํฌํธ
$ npm run test:coverage
--------------------------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
--------------------------|---------|----------|---------|---------|
All files | 92.5 | 88.3 | 95.2 | 92.8 |
auth/ | 95.0 | 90.0 | 100.0 | 95.0 |
middleware.ts | 95.0 | 90.0 | 100.0 | 95.0 |
routes.ts | 95.0 | 90.0 | 100.0 | 95.0 |
utils/ | 90.0 | 85.0 | 90.0 | 90.0 |
password.ts | 90.0 | 85.0 | 90.0 | 90.0 |
--------------------------|---------|----------|---------|---------|
Constraints
๋ฐ๋์ ์ง์ผ์ผ ํ ๊ท์น๊ณผ ๊ธ์ง ์ฌํญ์ ๋ช
์ํฉ๋๋ค.
ํ์ ๊ท์น (MUST)
-
ํ
์คํธ ๊ฒฉ๋ฆฌ: ๊ฐ ํ
์คํธ๋ ๋
๋ฆฝ์ ์ผ๋ก ์คํ ๊ฐ๋ฅํด์ผ ํจ
- beforeEach/afterEach๋ก ์ํ ์ด๊ธฐํ
- ํ
์คํธ ์์์ ์์กดํ์ง ์์
-
๋ช
ํํ ํ
์คํธ๋ช
: ํ
์คํธ๊ฐ ๋ฌด์์ ๊ฒ์ฆํ๋์ง ์ด๋ฆ์์ ์ ์ ์์ด์ผ ํจ
- โ
'should reject duplicate email'
- โ 'test1'
-
AAA ํจํด: Arrange(์ค๋น) - Act(์คํ) - Assert(๊ฒ์ฆ) ๊ตฌ์กฐ
- ๊ฐ๋
์ฑ ํฅ์
- ํ
์คํธ ์๋ ๋ช
ํํ
๊ธ์ง ์ฌํญ (MUST NOT)
-
ํ๋ก๋์
DB ์ฌ์ฉ ๊ธ์ง: ํ
์คํธ๋ ๋ณ๋ DB ๋๋ in-memory DB ์ฌ์ฉ
- ์ค์ ๋ฐ์ดํฐ ์์ค ์ํ
- ํ
์คํธ ๊ฒฉ๋ฆฌ ๋ถ๊ฐ
-
์ค์ ์ธ๋ถ API ํธ์ถ ๊ธ์ง: ์ธ๋ถ ์๋น์ค๋ ๋ชจํน
- ๋คํธ์ํฌ ์์กด์ฑ ์ ๊ฑฐ
- ํ
์คํธ ์๋ ํฅ์
- ๋น์ฉ ์ ๊ฐ
-
Sleep/Timeout ๋จ์ฉ ๊ธ์ง: ์๊ฐ ๊ธฐ๋ฐ ํ
์คํธ๋ ๊ฐ์ง ํ์ด๋จธ ์ฌ์ฉ
- jest.useFakeTimers()
- ํ
์คํธ ์๋ ์ ํ ๋ฐฉ์ง
๋ณด์ ๊ท์น
- ๋ฏผ๊ฐ์ ๋ณด ํ๋์ฝ๋ฉ ๊ธ์ง: ํ
์คํธ ์ฝ๋์๋ API ํค, ๋น๋ฐ๋ฒํธ ํ๋์ฝ๋ฉ ๊ธ์ง
- ํ๊ฒฝ๋ณ์ ๋ถ๋ฆฌ: .env.test ํ์ผ ์ฌ์ฉ
Examples
์์ 1: Python FastAPI ํ
์คํธ (Pytest)
์ํฉ: FastAPI REST API ํ
์คํธ
์ฌ์ฉ์ ์์ฒญ:
FastAPI๋ก ๋ง๋ ์ฌ์ฉ์ API๋ฅผ pytest๋ก ํ
์คํธํด์ค.
์ต์ข
๊ฒฐ๊ณผ:
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.main import app
from app.database import Base, get_db
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@pytest.fixture(scope="function")
def db_session():
Base.metadata.create_all(bind=engine)
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
Base.metadata.drop_all(bind=engine)
@pytest.fixture(scope="function")
def client(db_session):
def override_get_db():
try:
yield db_session
finally:
db_session.close()
app.dependency_overrides[get_db] = override_get_db
yield TestClient(app)
app.dependency_overrides.clear()
def test_register_user_success(client):
response = client.post("/auth/register", json={
"email": "test@example.com",
"username": "testuser",
"password": "Password123!"
})
assert response.status_code == 201
assert "access_token" in response.json()
assert response.json()["user"]["email"] == "test@example.com"
def test_register_duplicate_email(client):
client.post("/auth/register", json={
"email": "test@example.com",
"username": "user1",
"password": "Password123!"
})
response = client.post("/auth/register", json={
"email": "test@example.com",
"username": "user2",
"password": "Password123!"
})
assert response.status_code == 409
assert "already exists" in response.json()["detail"]
def test_login_success(client):
client.post("/auth/register", json={
"email": "test@example.com",
"username": "testuser",
"password": "Password123!"
})
response = client.post("/auth/login", json={
"email": "test@example.com",
"password": "Password123!"
})
assert response.status_code == 200
assert "access_token" in response.json()
def test_protected_route_without_token(client):
response = client.get("/auth/me")
assert response.status_code == 401
def test_protected_route_with_token(client):
register_response = client.post("/auth/register", json={
"email": "test@example.com",
"username": "testuser",
"password": "Password123!"
})
token = register_response.json()["access_token"]
response = client.get("/auth/me", headers={
"Authorization": f"Bearer {token}"
})
assert response.status_code == 200
assert response.json()["email"] == "test@example.com"
Best practices
ํ์ง ํฅ์
-
TDD (Test-Driven Development): ์ฝ๋ ์์ฑ ์ ์ ํ
์คํธ ๋จผ์
- ์๊ตฌ์ฌํญ ๋ช
ํํ
- ์ค๊ณ ๊ฐ์
- ๋์ ์ปค๋ฒ๋ฆฌ์ง ์์ฐ์ค๋ฝ๊ฒ ๋ฌ์ฑ
-
Given-When-Then ํจํด: BDD ์คํ์ผ๋ก ํ
์คํธ ์์ฑ
it('should return 404 when user not found', async () => {
const nonExistentId = 'non-existent-uuid';
const response = await request(app).get(`/users/${nonExistentId}`);
expect(response.status).toBe(404);
});
-
Test Fixtures: ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ํ
์คํธ ๋ฐ์ดํฐ
const validUser = {
email: 'test@example.com',
username: 'testuser',
password: 'Password123!'
};
ํจ์จ์ฑ ๊ฐ์
- ๋ณ๋ ฌ ์คํ: Jest์
--maxWorkers ์ต์
์ผ๋ก ํ
์คํธ ์๋ ํฅ์
- Snapshot Testing: UI ์ปดํฌ๋ํธ๋ JSON ์๋ต ์ค๋
์ท ์ ์ฅ
- Coverage ์๊ณ๊ฐ: jest.config.js์์ ์ต์ ์ปค๋ฒ๋ฆฌ์ง ๊ฐ์
์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ (Common Issues)
๋ฌธ์ 1: ํ
์คํธ ๊ฐ ์ํ ๊ณต์ ๋ก ์ธํ ์คํจ
์ฆ์: ๊ฐ๋ณ ์คํ์ ์ฑ๊ณตํ์ง๋ง ์ ์ฒด ์คํ ์ ์คํจ
์์ธ: beforeEach/afterEach ๋๋ฝ์ผ๋ก DB ์ํ ๊ณต์
ํด๊ฒฐ๋ฐฉ๋ฒ:
beforeEach(async () => {
await db.migrate.rollback();
await db.migrate.latest();
});
๋ฌธ์ 2: "Jest did not exit one second after the test run"
์ฆ์: ํ
์คํธ ์๋ฃ ํ ํ๋ก์ธ์ค๊ฐ ์ข
๋ฃ๋์ง ์์
์์ธ: DB ์ฐ๊ฒฐ, ์๋ฒ ๋ฑ์ด ์ ๋ฆฌ๋์ง ์์
ํด๊ฒฐ๋ฐฉ๋ฒ:
afterAll(async () => {
await db.destroy();
await server.close();
});
๋ฌธ์ 3: ๋น๋๊ธฐ ํ
์คํธ ํ์์์
์ฆ์: "Timeout - Async callback was not invoked"
์์ธ: async/await ๋๋ฝ ๋๋ Promise ๋ฏธ์ฒ๋ฆฌ
ํด๊ฒฐ๋ฐฉ๋ฒ:
it('should work', () => {
request(app).get('/users');
});
it('should work', async () => {
await request(app).get('/users');
});
References
๊ณต์ ๋ฌธ์
ํ์ต ์๋ฃ
๋๊ตฌ
Metadata
๋ฒ์
- ํ์ฌ ๋ฒ์ : 1.0.0
- ์ต์ข
์
๋ฐ์ดํธ: 2025-01-01
- ํธํ ํ๋ซํผ: Claude, ChatGPT, Gemini
๊ด๋ จ ์คํฌ
ํ๊ทธ
#testing #backend #Jest #Pytest #unit-test #integration-test #TDD #API-test