DEV Community

Cover image for 테스트를 위한 API 모의(Mock) 방법: 실전 가이드
Rihpig
Rihpig

Posted on • Originally published at apidog.com

테스트를 위한 API 모의(Mock) 방법: 실전 가이드

실시간 API에 의존하는 테스트는 잘못된 이유로 실패하기 쉽습니다. 스테이징 서버가 다운되거나, 서드파티 속도 제한이 걸리거나, 팀원이 레코드를 변경하면 코드가 정상이어도 테스트 스위트가 실패합니다. API 모의(mocking)는 실제 엔드포인트를 제어 가능한 테스트용 엔드포인트로 대체해, 매번 원하는 응답을 안정적으로 반환하게 합니다.

지금 Apidog 사용해 보기

이 글에서는 테스트를 위해 API를 모의하는 구현 단계를 다룹니다. 스키마를 정의하고, 모의 응답을 만들고, 테스트 코드가 모의 서버를 바라보게 설정한 뒤, 실제 서버에서는 재현하기 어려운 오류 경로까지 검증합니다. 예제는 작은 주문 관리 API를 사용하지만, 같은 흐름은 REST와 GraphQL 서비스 모두에 적용할 수 있습니다.

API 모의가 적절한 경우

네트워크나 실제 서버 상태가 아니라 내 코드의 동작을 검증하려는 테스트에서는 API를 모의하십시오. 단위 테스트와 대부분의 통합 테스트가 여기에 해당합니다.

예를 들어 클라이언트 코드가 다음을 처리하는지 확인할 수 있습니다.

  • 200 응답을 올바르게 파싱하는가
  • 503에서 재시도하는가
  • 404에서 명확한 오류 메시지를 표시하는가
  • 지연 응답에서 타임아웃 처리를 하는가

이런 검증에는 실제 서버가 필요하지 않습니다.

반대로 실제 API는 계약 테스트와 얇은 엔드투엔드 테스트에 사용하십시오. 모의가 실제 API와 계속 일치하는지 확인하기 위해서입니다. 모든 테스트를 모의로만 실행하면 프로덕션 API가 깨져도 테스트는 계속 통과할 수 있습니다.

정리하면 다음과 같습니다.

  • 속도와 격리가 필요하면 모의 사용
  • 실제 계약 검증이 필요하면 실제 API 호출

더 자세한 기준은 API 모의가 효과적인 시나리오모의 서버와 실제 서버의 차이점을 참고하십시오.

한눈에 보는 5단계 워크플로우

테스트용 API 모의는 언어나 프레임워크와 관계없이 대체로 같은 순서로 진행됩니다.

  1. 스키마 정의: 실제 응답 형태를 OpenAPI 등으로 정의합니다.
  2. 모의 응답 생성: 정적 또는 동적 응답을 만듭니다.
  3. 모의 서버 실행: 응답을 제공할 로컬 또는 클라우드 서버를 실행합니다.
  4. 테스트를 모의로 지정: 테스트 코드의 기본 URL을 모의 서버로 바꿉니다.
  5. 오류 경로 테스트: 404, 429, 500, 지연 응답 등을 검증합니다.

아래 예제에서는 GET /orders/{id} 엔드포인트를 가진 주문 관리 API를 사용합니다.

1단계: 스키마 정의

모의는 실제 응답 구조와 일치할 때만 가치가 있습니다. 먼저 스키마를 정의하십시오. 이미 OpenAPI 문서가 있다면 그것을 사용하고, 없다면 테스트 대상 엔드포인트부터 최소한으로 작성합니다.

예를 들어 GET /orders/{id}는 다음과 같이 정의할 수 있습니다.

paths:
  /orders/{id}:
    get:
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string }
                  status: { type: string, enum: [pending, shipped, delivered] }
                  total: { type: number }
                  items: { type: array, items: { type: object } }
        '404':
          description: Order not found
Enter fullscreen mode Exit fullscreen mode

이 스키마는 두 가지 역할을 합니다.

  • 모의 서버가 어떤 필드를 반환해야 하는지 알려줍니다.
  • API 계약의 단일 진실 공급원(single source of truth)이 됩니다.

백엔드가 응답 계약을 변경하면 스키마를 업데이트하고, 그 스키마에서 모의를 다시 생성합니다. 스키마 우선 접근은 API 계약 테스트를 유지하는 핵심입니다.

2단계: 모의 응답 생성

모의 응답은 크게 두 가지 방식으로 만들 수 있습니다.

정적 응답

정적 응답은 고정된 JSON 페이로드입니다.

{
  "id": "order_8842",
  "status": "shipped",
  "total": 149.99,
  "items": [
    {
      "sku": "sku_001",
      "name": "Keyboard",
      "quantity": 1
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

정적 응답은 예측 가능하므로 단언(assertion)이 명확합니다. 특정 값을 기대하는 단위 테스트에 적합합니다.

동적 응답

동적 응답은 요청마다 값을 생성합니다. 예를 들어 다음과 같은 필드를 자동 생성할 수 있습니다.

  • id: UUID 또는 주문 ID 형식
  • status: pending, shipped, delivered 중 하나
  • total: 현실적인 금액
  • items: 여러 개의 품목 배열

동적 데이터는 하나의 고정 페이로드로는 찾기 어려운 버그를 드러낼 수 있습니다. 예를 들어 긴 문자열, 예상보다 큰 배열, 특정 필드의 누락 또는 null 처리 문제를 찾는 데 유용합니다.

대부분의 팀은 두 방식을 함께 사용합니다.

  • 핵심 로직 단언에는 정적 페이로드
  • 입력 다양성 검증에는 동적 페이로드

Apidog를 사용하면 스키마를 기반으로 모의 엔드포인트를 자동 생성할 수 있습니다. Apidog는 email, phone, avatar 같은 필드 이름을 적절한 데이터 유형과 매칭해 현실적인 응답을 생성합니다.

직접 페이로드를 작성할 때는 프로덕션과 가까운 값을 사용하십시오. 예를 들어 모든 테스트 주문을 다음처럼 만들면 버그를 숨길 수 있습니다.

{
  "id": "order_test",
  "status": "pending",
  "total": 0,
  "items": []
}
Enter fullscreen mode Exit fullscreen mode

대신 실제에 가까운 금액, 여러 개의 품목, 실제 enum 값을 사용하십시오. 모의 데이터가 실제 응답에 가까울수록 테스트의 신뢰도가 높아집니다.

3단계: 모의 서버 실행

모의 응답을 만들었다면 해당 응답을 제공할 서버가 필요합니다. 선택지는 크게 두 가지입니다.

로컬 모의 서버

로컬 모의 서버는 보통 localhost 또는 127.0.0.1의 특정 포트에서 실행됩니다. 빠르고 오프라인에서도 동작하므로 단위 테스트와 통합 테스트에 적합합니다.

예를 들어 Prism을 사용하면 OpenAPI 파일에서 바로 모의 서버를 실행할 수 있습니다.

prism mock openapi.yaml
# Mock server listening on http://127.0.0.1:4010
Enter fullscreen mode Exit fullscreen mode

이제 테스트에서는 http://127.0.0.1:4010을 API 기본 URL로 사용하면 됩니다.

클라우드 모의 서버

클라우드 모의 서버는 공개 URL을 제공합니다. 다음 상황에서 유용합니다.

  • 모바일 앱이 개발자 노트북의 로컬 서버에 접근할 수 없는 경우
  • CI 러너가 로컬 머신에 접근할 수 없는 경우
  • 외부 협업자와 동일한 모의 API를 공유해야 하는 경우

Apidog는 프로젝트별 호스팅된 클라우드 모의 URL을 제공하므로, 서로 다른 네트워크의 팀원도 같은 모의 엔드포인트를 호출할 수 있습니다.

테스트 실행에는 가능한 한 로컬 모의 서버를 우선 사용하십시오. 네트워크 지연이 없고, 공유 상태로 인해 빌드가 서로 영향을 주는 문제를 줄일 수 있습니다. 클라우드 모의는 데모, 모바일 테스트, 외부 협업에 적합합니다.

4단계: 테스트를 모의로 지정

테스트 코드가 프로덕션 API 대신 모의 서버를 호출하도록 설정합니다. 가장 깔끔한 방식은 기본 URL을 환경 변수로 분리하는 것입니다.

// orderClient.test.js
import { getOrder } from './orderClient.js';

const BASE_URL = process.env.API_BASE_URL || 'http://127.0.0.1:4010';

test('parses a shipped order', async () => {
  const order = await getOrder('order_8842', BASE_URL);

  expect(order.status).toBe('shipped');
  expect(typeof order.total).toBe('number');
});
Enter fullscreen mode Exit fullscreen mode

클라이언트 함수는 기본 URL을 인수로 받습니다.

// orderClient.js
export async function getOrder(id, baseUrl) {
  const response = await fetch(`${baseUrl}/orders/${id}`);

  if (!response.ok) {
    throw new Error(`Failed to fetch order: ${response.status}`);
  }

  return response.json();
}
Enter fullscreen mode Exit fullscreen mode

CI에서는 테스트 실행 전에 API_BASE_URL을 모의 서버 주소로 설정합니다.

API_BASE_URL=http://127.0.0.1:4010 npm test
Enter fullscreen mode Exit fullscreen mode

이 패턴의 장점은 모의 로직이 애플리케이션 코드 안으로 들어오지 않는다는 점입니다. 프로덕션 코드는 자신이 모의 서버와 통신하는지 알 필요가 없습니다.

CI/CD 파이프라인에서 API 테스트를 실행할 때도 같은 접근을 사용할 수 있습니다. 자세한 내용은 CI/CD에서 API 테스트를 자동화를 참고하십시오.

5단계: 오류 경로 테스트

API 모의의 가장 큰 장점 중 하나는 실제 서버에서 재현하기 어려운 오류를 명령에 따라 만들 수 있다는 점입니다.

실제 서버는 원하는 순간에 500을 반환하지 않습니다. 하지만 모의 서버는 가능합니다.

다음 시나리오를 테스트에 포함하십시오.

시나리오 모의 반환 단언 내용
기록 없음 404 클라이언트가 명확한 "찾을 수 없음" 오류를 발생시킴
서버 오류 500 클라이언트가 재시도한 뒤 폴백을 표시함
속도 제한 Retry-After 헤더와 함께 429 클라이언트가 적절히 후퇴(backoff)함
느린 응답 5초 지연 후 200 클라이언트가 타임아웃되고 복구됨
잘못된 본문 손상된 JSON과 함께 200 클라이언트가 충돌하지 않고 정상적으로 실패함

예를 들어 테스트에서는 다음과 같은 실패 케이스를 검증할 수 있습니다.

test('throws a clear error when order is not found', async () => {
  await expect(getOrder('order_404', BASE_URL))
    .rejects
    .toThrow('Failed to fetch order: 404');
});
Enter fullscreen mode Exit fullscreen mode

Apidog의 고급 모의 규칙을 사용하면 요청 조건에 따라 다른 응답을 반환할 수 있습니다. 예를 들어 order_404에 대한 요청은 404를 반환하고, 다른 ID는 정상적인 200 응답을 반환하도록 구성할 수 있습니다.

이렇게 하면 하나의 모의 엔드포인트로 성공 경로와 실패 경로를 모두 테스트할 수 있습니다. 여기에 API 단언을 결합하면 상태 코드뿐 아니라 실제 클라이언트 동작까지 검증할 수 있습니다.

성장하는 테스트 스위트에서 모의 정리하기

모의 엔드포인트가 하나일 때는 관리가 쉽습니다. 하지만 테스트 스위트가 커지고 수십 개, 수백 개의 모의가 생기면 구조가 필요합니다.

다음 원칙을 적용하십시오.

실제 서비스 기준으로 그룹화하기

모의를 테스트 파일 기준이 아니라 실제 서비스 기준으로 묶으십시오.

예를 들어 다음처럼 구성할 수 있습니다.

mocks/
  orders/
    order-shipped.json
    order-not-found.json
    order-rate-limited.json
  payments/
    payment-approved.json
    payment-declined.json
Enter fullscreen mode Exit fullscreen mode

결제 API가 변경되면 여러 테스트 파일을 뒤지는 대신 payments 모의만 업데이트하면 됩니다.

시나리오가 드러나는 이름 사용하기

response1.json, test-data.json 같은 이름은 피하십시오.

대신 다음처럼 실패 원인을 바로 알 수 있는 이름을 사용하십시오.

  • order-shipped
  • order-not-found
  • order-rate-limited
  • payment-declined

실패한 테스트를 읽을 때 어떤 조건을 검증하는지 바로 파악할 수 있습니다.

모의 정의를 버전 관리하기

모의는 테스트의 일부입니다. 코드와 마찬가지로 리뷰되고 추적되어야 합니다.

  • 모의 파일을 저장소에 포함
  • API 응답 변경 PR에서 모의 변경도 함께 리뷰
  • 스키마 변경 시 관련 모의 재생성 또는 업데이트

기본 응답을 만들고 필요한 필드만 덮어쓰기

모든 테스트마다 완전히 새로운 페이로드를 만들면 유지보수가 어려워집니다. 대부분의 테스트는 같은 기본 객체에서 한두 필드만 다릅니다.

예를 들어 기본 주문 객체를 만들고:

const baseOrder = {
  id: 'order_8842',
  status: 'shipped',
  total: 149.99,
  items: [
    {
      sku: 'sku_001',
      name: 'Keyboard',
      quantity: 1
    }
  ]
};
Enter fullscreen mode Exit fullscreen mode

테스트별로 필요한 필드만 바꿉니다.

const pendingOrder = {
  ...baseOrder,
  status: 'pending'
};

const expensiveOrder = {
  ...baseOrder,
  total: 1299.99
};
Enter fullscreen mode Exit fullscreen mode

이 방식은 테스트를 읽기 쉽게 만들고, 계약 변경 시 수정 범위를 줄입니다. API 자동화를 위한 테스트 스위트를 유지보수 가능하게 만드는 원칙은 그 뒤의 모의에도 그대로 적용됩니다.

모의를 정직하게 유지하기

모의는 시간이 지나면 실제 API와 달라질 수 있습니다. 백엔드가 필드를 추가하거나, totalamount로 바꾸거나, enum 값을 변경해도 모의는 예전 응답을 계속 반환할 수 있습니다.

이 경우 테스트는 통과하지만 프로덕션은 실패합니다. 모의 기반 테스트에서 가장 흔하고 위험한 문제입니다.

이를 방지하려면 다음 두 가지를 적용하십시오.

1. 동일한 스키마에서 모의를 생성하기

백엔드가 게시하는 OpenAPI 문서를 기준으로 모의를 생성하십시오. 직접 작성한 JSON 모의는 스키마 변경을 자동으로 따라가지 못합니다.

OpenAPI 파일에서 모의를 생성하면 API 계약 변경 시 모의도 같은 기준으로 갱신할 수 있습니다.

2. 실제 API에 대해 계약 테스트 실행하기

모의 테스트와 별도로 작은 계약 테스트 세트를 실제 API에 대해 정기적으로 실행하십시오.

이 테스트의 목적은 비즈니스 로직 검증이 아니라 다음을 확인하는 것입니다.

  • 실제 응답이 스키마와 일치하는가
  • 필수 필드가 유지되는가
  • 타입이 변경되지 않았는가
  • enum 값이 예상 범위에 있는가

계약 테스트가 실패하면 모의가 오래되었거나 API 계약이 변경된 것입니다. 사용자가 문제를 발견하기 전에 테스트에서 먼저 감지할 수 있습니다.

코드 리뷰에서도 모의를 확인하십시오. API 응답을 바꾸는 PR이라면 관련 스키마와 모의도 함께 변경되어야 합니다. 모의를 일회용 테스트 도우미가 아니라 API 계약의 일부로 취급해야 장기적으로 신뢰할 수 있습니다.

스키마, 모의 서버, 테스트 스위트를 한 환경에서 관리하고 싶다면 Apidog를 다운로드해 사용할 수 있습니다. Apidog는 API 디자인, 모의 서버, 테스트 스위트를 동기화하여 테스트가 현재 계약을 기준으로 실행되도록 돕습니다. 다른 선택지를 비교하려면 REST API 모의 도구 정리를 참고하십시오.

자주 묻는 질문

모든 테스트에 API를 모의해야 할까요?

아닙니다. 자신의 코드를 확인하는 단위 테스트와 통합 테스트에는 모의를 사용하십시오. 대신 모의가 실제 API와 일치하는지 확인하기 위한 소규모 계약 테스트와 엔드투엔드 테스트는 실제 API를 호출하도록 유지해야 합니다. 모든 것을 모의하면 계약 불일치를 숨길 수 있습니다.

정적 모의 응답과 동적 모의 응답의 차이점은 무엇인가요?

정적 응답은 변하지 않는 고정 JSON 페이로드입니다. 예측 가능한 단언에 적합합니다. 동적 응답은 요청마다 현실적인 값을 생성하며, 하나의 고정 페이로드가 놓칠 수 있는 버그를 찾는 데 유용합니다. 대부분의 팀은 두 방식을 함께 사용합니다.

모의가 정확하게 유지되도록 하려면 어떻게 해야 하나요?

백엔드가 사용하는 동일한 스키마, 가능하면 OpenAPI 문서에서 모의를 생성하십시오. 그런 다음 실제 API에 대해 정기적으로 계약 테스트를 실행해 라이브 응답이 스키마와 계속 일치하는지 확인하십시오. 실패하면 모의 또는 스키마를 업데이트해야 합니다.

모의가 느리거나 실패하는 응답을 시뮬레이션할 수 있나요?

예. 모의 서버를 사용하면 500, Retry-After 헤더가 포함된 429, 지연된 200, 손상된 JSON 응답 등을 구성할 수 있습니다. 이를 통해 실제 서버에서 재현하기 어려운 재시도, 타임아웃, 오류 처리 로직을 검증할 수 있습니다.

테스트에는 로컬 모의 서버와 클라우드 모의 서버 중 어떤 것을 사용해야 하나요?

일반적인 테스트 실행에는 로컬 모의 서버를 사용하십시오. 빠르고 네트워크 지연이 없으며 빌드 간 공유 상태를 피할 수 있습니다. 모바일 기기, CI 러너, 외부 협업자가 개발자 머신에 접근하지 않고 모의를 호출해야 한다면 클라우드 모의 서버를 사용하십시오.

Top comments (0)