Mocking APIs#
목 API(Mocking API)는 소프트웨어 개발 과정에서 실제 API를 대체하여 테스트, 개발, 디버깅을 용이하게 하는 가상의 API이다.
목 API(Mocking API)는 현대 소프트웨어 개발에서 필수적인 도구로, 개발 속도를 높이고 의존성을 줄이며 다양한 시나리오를 테스트할 수 있게 해준다. 실제 API와의 일관성을 유지하고, 다양한 상황을 시뮬레이션하며, 체계적인 전환 전략을 갖추면 목 API(Mocking API)는 개발 프로세스의 효율성을 크게 향상시킬 수 있다.
목 API(Mocking API)를 효과적으로 활용하면 프론트엔드와 백엔드 개발을 병렬화하고, 품질은 높이면서도 개발 시간은 단축할 수 있다. 다만 실제 API와의 차이를 인식하고, 최종적으로는 실제 환경에서의 철저한 테스트가 필요함을 기억해야 한다.
목 API(Mocking API)의 기본 개념#
목 API(Mocking API)는 실제 API의 동작을 시뮬레이션하는 가상의 인터페이스이다.
실제 서버나 데이터베이스에 연결하지 않고도 API 응답을 모방할 수 있어, 개발자가 의존성 없이 코드를 테스트하고 개발할 수 있게 해준다.
목킹(Mocking)이란 단어는 ‘모방하다’라는 의미로, 프로그래밍에서는 실제 객체나 서비스의 행동을 흉내 내는 것을 의미한다. API 목킹은 이러한 모방 기술을 API에 적용한 것이다.
목 API(Mocking API)를 사용하는 이유#
- 의존성 제거: 외부 서비스나 API에 의존하지 않고 독립적으로 개발할 수 있다.
- 개발 속도 향상: 백엔드 API가 완성되기 전에도 프론트엔드 개발을 진행할 수 있다.
- 테스트 안정성: 실제 API는 네트워크 지연, 서버 다운타임 등의 변수가 있지만, 목 API는 일관된 응답을 제공한다.
- 다양한 시나리오 테스트: 오류 상황, 지연 시간, 다양한 응답 형태 등을 쉽게 시뮬레이션할 수 있다.
- 비용 절감: API 호출에 요금이 부과되는 경우, 목 API를 사용하면 개발 과정에서 비용을 절약할 수 있다.
- 오프라인 개발: 인터넷 연결이 없는 환경에서도 개발을 계속할 수 있다.
목 API의 종류#
로컬 목 서버#
자체 개발 환경에서 실행되는 서버로, 실제 API의 엔드포인트와 응답을 모방한다.
주로 Express(Node.js), Flask(Python) 등을 사용하여 구현한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // Express를 사용한 간단한 목 서버 예제
const express = require('express');
const app = express();
// 사용자 목록을 반환하는 목 API 엔드포인트
app.get('/api/users', (req, res) => {
// 실제 데이터베이스 대신 하드코딩된 응답 제공
const mockUsers = [
{ id: 1, name: '김철수', email: 'kim@example.com' },
{ id: 2, name: '이영희', email: 'lee@example.com' }
];
res.json(mockUsers);
});
app.listen(3000, () => {
console.log('Mock API server is running on port 3000');
});
|
서비스형 목 API (Mock as a Service)#
클라우드 기반의 목 API 서비스로, 웹 인터페이스를 통해 목 API를 생성하고 관리할 수 있다.
대표적인 서비스로는 Mockoon, Postman, MockAPI 등이 있다.
라이브러리 기반 목킹#
테스트 코드 내에서 HTTP 요청을 가로채고 목 응답을 반환하는 라이브러리를 사용한다.
Jest, Nock, MSW(Mock Service Worker) 등이 대표적이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // Jest와 axios-mock-adapter를 사용한 API 목킹 예제
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
// axios 인스턴스에 대한 목 어댑터 생성
const mock = new MockAdapter(axios);
// GET 요청에 대한 목 응답 설정
mock.onGet('/api/users').reply(200, [
{ id: 1, name: '김철수', email: 'kim@example.com' },
{ id: 2, name: '이영희', email: 'lee@example.com' }
]);
// 이제 axios.get('/api/users')는 위의 목 데이터를 반환합니다
|
브라우저 서비스 워커 기반 목킹#
브라우저의 서비스 워커를 사용하여 네트워크 요청을 가로채고 목 응답을 제공한다.
MSW(Mock Service Worker)가 대표적이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // MSW를 사용한 브라우저 목킹 예제
import { setupWorker, rest } from 'msw';
// 목 핸들러 정의
const handlers = [
rest.get('/api/users', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json([
{ id: 1, name: '김철수', email: 'kim@example.com' },
{ id: 2, name: '이영희', email: 'lee@example.com' }
])
);
})
];
// 브라우저에서 서비스 워커 설정
const worker = setupWorker(...handlers);
worker.start();
|
효과적인 목 API 구현 방법#
실제 API와 일치하는 구조 유지#
목 API는 실제 API와 동일한 엔드포인트, 요청/응답 구조, 데이터 형식을 가져야 한다.
이는 나중에 실제 API로 전환할 때 코드 변경을 최소화한다.
다양한 시나리오 지원#
성공 케이스뿐만 아니라 오류 상황, 지연 응답, 부분 데이터 등 다양한 시나리오를 시뮬레이션할 수 있어야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // 다양한 상황을 시뮬레이션하는 목 API 예제
app.get('/api/users/:id', (req, res) => {
const userId = parseInt(req.params.id);
// 지연 시뮬레이션
setTimeout(() => {
// 특정 ID에 대한 오류 시뮬레이션
if (userId === 999) {
return res.status(500).json({ error: '서버 오류' });
}
// 존재하지 않는 사용자 시뮬레이션
if (userId > 10) {
return res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
}
// 정상적인 응답
return res.json({ id: userId, name: `사용자${userId}`, email: `user${userId}@example.com` });
}, userId === 5 ? 2000 : 100); // ID가 5인 경우 지연 시간 증가
});
|
상태 관리 기능 구현#
필요한 경우, 목 API에서도 데이터 생성, 수정, 삭제 등의 상태 변경을 유지할 수 있어야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| // 상태를 유지하는 목 API 예제
const express = require('express');
const app = express();
app.use(express.json());
// 메모리 내 사용자 데이터 저장소
let users = [
{ id: 1, name: '김철수', email: 'kim@example.com' },
{ id: 2, name: '이영희', email: 'lee@example.com' }
];
// 사용자 목록 조회
app.get('/api/users', (req, res) => {
res.json(users);
});
// 사용자 추가
app.post('/api/users', (req, res) => {
const newUser = {
id: users.length + 1,
...req.body
};
users.push(newUser);
res.status(201).json(newUser);
});
// 사용자 삭제
app.delete('/api/users/:id', (req, res) => {
const userId = parseInt(req.params.id);
const userIndex = users.findIndex(user => user.id === userId);
if (userIndex === -1) {
return res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
}
users = users.filter(user => user.id !== userId);
res.status(204).end();
});
|
문서화와 공유#
팀 내에서 목 API의 사용법과 기능을 명확히 문서화하고 공유하여, 모든 팀원이 일관된 개발 환경을 갖도록 한다.
주요 목 API 도구와 라이브러리#
독립형 도구
- Mockoon: 데스크톱 애플리케이션으로, GUI를 통해 목 API를 쉽게 생성하고 관리할 수 있다.
- Postman: API 테스트 도구로, 목 서버 기능도 제공한다.
- JSON Server: JSON 파일을 기반으로 전체 가짜 REST API를 생성한다.
- WireMock: Java 기반의 강력한 목 서버로, 다양한 응답 패턴을 지원한다.
프로그래밍 언어별 라이브러리
- JavaScript/Node.js
- Nock: HTTP 서버 목킹 라이브러리
- MSW (Mock Service Worker): 서비스 워커를 통한 네트워크 요청 가로채기
- Mirage JS: 클라이언트 사이드 API 목킹 라이브러리
- Python
- responses: requests 라이브러리를 위한 목킹 도구
- HTTPretty: HTTP 요청 목킹 라이브러리
- Java
- MockMvc: Spring 애플리케이션에서 컨트롤러 테스트를 위한 도구
- Mockito: Java 목킹 프레임워크
실제 API로의 전환 전략#
목 API에서 실제 API로 원활하게 전환하려면 다음과 같은 전략이 필요하다:
1. 환경별 설정#
개발, 테스트, 프로덕션 환경에 따라 다른 API 엔드포인트를 사용하도록 설정한다.
1
2
3
4
| // 환경에 따른 API 엔드포인트 설정 예제
const API_BASE_URL = process.env.NODE_ENV === 'development'
? 'http://localhost:3000/api' // 개발 환경: 목 API
: 'https://api.example.com/api'; // 프로덕션 환경: 실제 API
|
점진적 전환#
전체 API를 한 번에 전환하는 대신, 엔드포인트별로 점진적으로 실제 API로 전환한다.
기능 플래그 사용#
코드 내에서 기능 플래그를 사용하여 목 API와 실제 API 사이를 쉽게 전환할 수 있게 한다.
1
2
3
4
5
6
7
8
9
10
| // 기능 플래그를 사용한 API 전환 예제
const useMockApi = config.features.useMockApi;
async function fetchUsers() {
if (useMockApi) {
return mockUserService.getUsers();
} else {
return realApiService.getUsers();
}
}
|
목 API의 한계와 주의사항#
실제 환경과의 차이
목 API는 실제 API의 모든 측면(성능, 오류 패턴, 시간 지연 등)을 완벽히 재현할 수 없다.
유지보수 부담
실제 API가 변경될 때마다 목 API도 업데이트해야 하므로 유지보수 부담이 있다.
통합 문제
목 API에서는 잘 작동하던 코드가 실제 API와 통합할 때 문제가 발생할 수 있다.
과도한 의존성
목 API에 너무 의존하면 실제 통합 테스트가 부족해질 수 있다.
목 API를 활용한 개발 워크플로우#
이상적인 개발 워크플로우는 다음과 같다:
- API 설계: 백엔드와 프론트엔드 팀이 함께 API 스펙을 정의한다.
- 목 API 구현: API 스펙을 기반으로 목 API를 구현한다.
- 병렬 개발: 프론트엔드 팀은 목 API를 사용해 개발하고, 백엔드 팀은 실제 API를 구현한다.
- 통합 테스트: 실제 API가 준비되면 점진적으로 전환하고 통합 테스트를 수행한다.
- 배포: 모든 통합 테스트가 통과하면 전체 시스템을 배포한다.
OpenAPI 스펙과 목 API#
OpenAPI(Swagger) 스펙은 목 API 생성을 크게 개선할 수 있다:
- 스펙 기반 자동 생성: OpenAPI 스펙을 기반으로 목 서버를 자동 생성할 수 있다.
- 일관성 보장: API 문서와 목 서버가 항상 일치하게 된다.
- 도구 지원: Swagger UI, Prism 등의 도구를 통해 쉽게 목 서버를 생성할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| # OpenAPI 스펙 예제
openapi: 3.0.0
info:
title: 사용자 API
version: 1.0.0
paths:
/users:
get:
summary: 사용자 목록 조회
responses:
'200':
description: 성공
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
email:
type: string
|
용어 정리#
참고 및 출처#
Mockoon Mockoon은 개발자가 빠르고 쉽게 API를 모킹(가짜로 구현)할 수 있는 오픈 소스 데스크톱 애플리케이션이다.
이 도구는 2017년에 처음 출시되었으며, API 개발 및 테스트 과정을 간소화하기 위해 설계되었다.
Mockoon의 주요 특징:
그래픽 사용자 인터페이스(GUI)로 코드 작성 없이 API 모킹 가능 윈도우, 맥, 리눅스 지원 크로스 플랫폼 애플리케이션 무료 오픈 소스 도구 CLI(Command Line Interface) 버전 제공 Docker 이미지 지원 Mockoon이 해결하는 개발 문제 백엔드 개발자로서 다음과 같은 상황에서 Mockoon이 매우 유용하다:
...
WireMock WireMock은 HTTP 기반 API를 위한 시뮬레이션 도구로, Java 환경에서 개발되었으나 다양한 플랫폼에서 활용 가능하다. 톰 애컬턴(Tom Akehurst)이 개발한 이 오픈소스 도구는 실제 서비스와 동일하게 동작하는 모의(Mock) API를 쉽게 구축할 수 있게 해준다.
WireMock의 주요 특징 독립 실행형 서버: 자체 HTTP 서버로 실행 가능 JUnit과의 통합: Java 테스트 코드에 내장하여 사용 가능 정확한 HTTP 응답 모방: 상태 코드, 헤더, 본문 등 완벽 모방 요청 매칭: URL, 헤더, 쿼리 파라미터, 본문 등 다양한 요소 기반 매칭 응답 템플릿팅: 동적 응답 생성 가능 상태 기반 행동: 이전 요청에 따라 다른 응답 반환 가능 요청 검증: 특정 요청이 발생했는지 확인 가능 장애 시뮬레이션: 지연, 오류, 손상된 응답 등 시뮬레이션 프록시 모드: 실제 서비스와 모킹 결합 가능 레코딩 기능: 실제 서비스의 응답을 기록하여 모킹에 활용 WireMock의 작동 원리 WireMock은 실제로 두 가지 주요 모드로 작동한다:
...