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)를 사용하는 이유

  1. 의존성 제거: 외부 서비스나 API에 의존하지 않고 독립적으로 개발할 수 있다.
  2. 개발 속도 향상: 백엔드 API가 완성되기 전에도 프론트엔드 개발을 진행할 수 있다.
  3. 테스트 안정성: 실제 API는 네트워크 지연, 서버 다운타임 등의 변수가 있지만, 목 API는 일관된 응답을 제공한다.
  4. 다양한 시나리오 테스트: 오류 상황, 지연 시간, 다양한 응답 형태 등을 쉽게 시뮬레이션할 수 있다.
  5. 비용 절감: API 호출에 요금이 부과되는 경우, 목 API를 사용하면 개발 과정에서 비용을 절약할 수 있다.
  6. 오프라인 개발: 인터넷 연결이 없는 환경에서도 개발을 계속할 수 있다.

목 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 도구와 라이브러리

  1. 독립형 도구

    • Mockoon: 데스크톱 애플리케이션으로, GUI를 통해 목 API를 쉽게 생성하고 관리할 수 있다.
    • Postman: API 테스트 도구로, 목 서버 기능도 제공한다.
    • JSON Server: JSON 파일을 기반으로 전체 가짜 REST API를 생성한다.
    • WireMock: Java 기반의 강력한 목 서버로, 다양한 응답 패턴을 지원한다.
  2. 프로그래밍 언어별 라이브러리

    1. JavaScript/Node.js
      • Nock: HTTP 서버 목킹 라이브러리
      • MSW (Mock Service Worker): 서비스 워커를 통한 네트워크 요청 가로채기
      • Mirage JS: 클라이언트 사이드 API 목킹 라이브러리
    2. Python
      • responses: requests 라이브러리를 위한 목킹 도구
      • HTTPretty: HTTP 요청 목킹 라이브러리
    3. 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의 한계와 주의사항

  1. 실제 환경과의 차이
    목 API는 실제 API의 모든 측면(성능, 오류 패턴, 시간 지연 등)을 완벽히 재현할 수 없다.

  2. 유지보수 부담
    실제 API가 변경될 때마다 목 API도 업데이트해야 하므로 유지보수 부담이 있다.

  3. 통합 문제
    목 API에서는 잘 작동하던 코드가 실제 API와 통합할 때 문제가 발생할 수 있다.

  4. 과도한 의존성
    목 API에 너무 의존하면 실제 통합 테스트가 부족해질 수 있다.

목 API를 활용한 개발 워크플로우

이상적인 개발 워크플로우는 다음과 같다:

  1. API 설계: 백엔드와 프론트엔드 팀이 함께 API 스펙을 정의한다.
  2. 목 API 구현: API 스펙을 기반으로 목 API를 구현한다.
  3. 병렬 개발: 프론트엔드 팀은 목 API를 사용해 개발하고, 백엔드 팀은 실제 API를 구현한다.
  4. 통합 테스트: 실제 API가 준비되면 점진적으로 전환하고 통합 테스트를 수행한다.
  5. 배포: 모든 통합 테스트가 통과하면 전체 시스템을 배포한다.

OpenAPI 스펙과 목 API

OpenAPI(Swagger) 스펙은 목 API 생성을 크게 개선할 수 있다:

  1. 스펙 기반 자동 생성: OpenAPI 스펙을 기반으로 목 서버를 자동 생성할 수 있다.
  2. 일관성 보장: API 문서와 목 서버가 항상 일치하게 된다.
  3. 도구 지원: 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

용어 정리

용어설명

참고 및 출처