API 모킹으로 테스트를 더 편리하게, Mock Service GUI 소개

Nov.21.2024 류현승

Web Frontend

여러분은 프론트엔드의 API 모킹 환경을 어떤 방식으로 구축하고, 사용하고 계신가요?

이 글에서는 API 모킹 환경을 개발자 친화적으로 개선해 나가는 과정을 다루며, 2023 우아한테크콘퍼런스에서 발표되었던 mock-service-gui의 최신 업데이트와 그 활용 사례들을 함께 소개합니다.

예상 독자

  • API 모킹 환경이 구축되어 있으나, 더 효과적으로 활용하고 싶은 분들
  • 무분별하게 늘어가는 목(Mock) 데이터로 인해 피로감을 느끼는 분들
  • 프로젝트에서 API 모킹 환경을 잘 활용하지 못하고 있는 분들
  • 테스트 환경에서 번거로운 API 모킹으로 불편함을 겪고 있는 분들
  • API 모킹 환경 도입에 관심이 있어 활용 사례를 탐색 중인 분들

프론트엔드와 API 모킹

프론트엔드 개발에서 API 모킹 환경은 다양한 이유로 필수적인 요소로 자리를 잡아가고 있습니다.

API 모킹이란?
프론트엔드에서 API 모킹은 실제 백엔드나 API 서버 없이도 가짜 데이터를 사용해 프론트엔드 개발과 테스트를 가능하게 하는 기술

백엔드가 완성되기 전에 프론트엔드 기능을 개발하고 독립적인 테스트를 할 수 있게 해주며, 외부 API와의 연동 테스트를 더 쉽게 진행할 수 있기 때문입니다.

특히, 협업이 중요한 대규모의 프로젝트나 여러 부서와 다양한 어드민이 존재하는 회사에서는 일관된 목 데이터를 제공함으로써, 다수의 인원이 작업할 때 발생할 수 있는 커뮤니케이션에 따른 병목과 테스트 환경 설정의 번거로움을 크게 줄일 수 있습니다.

만약 프론트엔드에서 API 모킹이 없다면, 위와 같이 개발을 위한 테스트 환경을 설정하는 과정에서 반복적인 요청과 작업이 발생했을 것입니다.

하지만, 이미 구축된 API 모킹 환경에서도 시간이 지남에 따라 여러 한계가 드러납니다.

점점 복잡해지는 API 구조와 다양한 정책을 반영하는 과정에서 목 데이터는 무분별하게 늘어나 이전 작업에 대한 이해가 없는 새로운 팀원에게는 진입 장벽으로 다가올 수 있으며, 목 데이터를 유지하고 사용하는 데 드는 시간과 노력은 점점 더 많이 소모됩니다.

이러한 피로감과 비효율성은 향후 개발 과정에서도 큰 부담으로 작용하여, 우리에게 도움을 주었던 API 모킹 환경은 결국 기술 부채로 변해, 개발 효율성을 저해하거나 사용 빈도가 점차 줄어 방치될 가능성이 커집니다.

개선 전 API 모킹 환경

제가 코어웹프론트개발팀에 합류했을 때, 팀에는 이미 API 모킹 환경이 갖추어져 있었습니다.

특히, 각 프로젝트에서 API 모킹의 사용 빈도가 높아, 모킹 환경 개선에 대한 필요성 또한 점점 커지고 있었고, 실제로 Axios Mock Adapter를 사용하던 중, 브라우저 네트워크 요청을 트래킹할 수 없는 제약으로 인해 디버깅이 어렵고 유지보수가 복잡해지는 문제가 있어, 이를 해결하고 더 나은 확장성을 확보하기 위해 MSW(Mock Service Worker)로 전환하는 과정을 진행하고 있었습니다.

MSW란?
MSW는 브라우저의 Service Worker를 활용해 네트워크 요청을 가로채고, 이를 기반으로 API 모킹 환경을 구축할 수 있는 강력한 도구입니다.

그러나 MSW로 전환하고 같은 시기에 진행하던 팀 표준 개발 환경을 구축해 가는 과정에서도, 여전히 API 모킹 환경의 컨벤션 지정이 번거롭고, 수많은 API 모킹 설정에 대한 가시성이 제공되지 않아 어떤 API가 어떤 응답을 제공하고 있는지 한눈에 파악하기 어려웠으며 이런 상황에서 모킹 동작을 수정하는 것은 큰 불편함으로 다가왔습니다.

이러한 경험과 의견들이 쌓여 Mock Service GUI를 개발하게 된 계기가 되었고, 그 과정에서 모킹 환경을 겪으며 어떤 불편함이 있었고, 이를 어떻게 해결해 나갔는지를 다음 목차를 통해 하나씩 살펴보겠습니다.

API 모킹 제어 시 불편함 개선하기

먼저, 코어웹프론트개발팀은 기능 조직(같은 전문 분야를 중심으로 구성한 조직)으로서 프론트엔드 개발자가 11명이고, 일부 저장소에서는 다양한 팀의 개발자들이 협업하는 환경도 존재하고 있습니다.

다양한 응답 케이스를 브라우저에서 디버깅하기 위해 매번 코드 수정을 거쳐 API 모킹 동작을 변경하고 있었으며, 각 개발자가 프로젝트 진행 상황에 맞춰 모킹 동작을 수정하고 있어, 서로의 설정에 영향을 미치고 코드 병합 과정에 불필요한 충돌이 생기는 일도 잦았습니다.

단순한 API 모킹 코드처럼 보이지만, API 모킹을 제어하는 과정에서 방대한 목 데이터를 탐색해야 하는 부담이 발생합니다.
특히, 목 데이터의 양이 늘어날수록 탐색과 수정 작업이 점점 비효율적이고 번거로워지는 문제가 두드러집니다.

또한, API 모킹 동작을 변경하려면 작업자가 특정 상황에서 어떤 API가 어떤 응답의 목 데이터를 반환해야 하는지 파악하고, 수많은 목 데이터 중 그에 적합한 목 데이터가 존재하는지 이전 히스토리까지 검토해야 하는 번거로움이 있었습니다.

이런 번거로운 과정들로 불편함이 점점 커졌고, 작업 효율성도 떨어졌습니다.

이러한 문제를 겪으며, “코드를 수정하지 않고, UI에서 바로 API 모킹을 제어할 수 있다면 어떨까?” 하는 생각을 하게 되었는데요. UI에서 API 모킹 동작을 직접 제어할 수 있다면, 서로의 작업물이 얽히는 문제나 코드 충돌을 줄일 수 있을 뿐 아니라, 설정을 바꿀 때마다 코드를 수정하는 번거로움도 줄일 수 있을 거라고 기대했습니다.

위 접근을 통해 브라우저에서 표기되는 UI를 통해 MSW의 API 모킹 동작을 제어하도록 구현한 것이 바로 Mock Service GUI입니다.

Mock Serivce GUI 2.0 동작 영상

Mock Service GUI는 브라우저 익스텐션이 아니라, 여러분의 프로젝트에 디펜던시로 설치하는 라이브러리로 제공됩니다.

MSW의 내부 클래스를 오버라이드하여 추가적인 메서드를 제공하는 방식으로 개발되었기 때문에, MSW를 이미 사용 중이라면 큰 코드 변경 없이 Mock Service GUI를 설치하면 프로젝트에서 사용 중이던 목 데이터를 빠르게 불러올 수 있습니다.

또한, UI를 제공함으로써 API와 목 데이터를 1:n 단위로 매핑할 수 있게 되었으며, 또한 각 상황에 대한 설명을 목 데이터에 직접 지정할 수 있어, 프로젝트를 처음 작업하는 개발자도 API 모킹 동작을 직관적으로 빠르게 파악하고 테스트해볼 수 있게 되었습니다. 이를 통해 불필요한 히스토리 파악에 소요되는 시간을 줄일 수 있었습니다.


프로파일 기능을 통해 특정 상황에 사용되는 다수의 API에서 어떠한 응답이 나와야하는지 매핑하여
여러 API 모킹을 동시에 제어할 수도 있습니다.

그리고 브라우저에서 API 모킹 설정을 제어할 수 있게 되면서, 각 개발자는 프로젝트에 저장된 목 데이터를 기반으로 자신의 진행 상황에 맞춘 API 모킹 설정을 브라우저의 로컬스토리지에 별도로 저장하고 관리할 수 있게 되었습니다.

이를 통해 팀원 간 설정이 충돌할 염려 없이, 각자 독립적으로 자신의 진행 상황에 맞춰 작업을 진행할 수 있게 되었으며, 그 결과 협업이 필요한 상황에도 특별한 컨벤션을 숙지할 필요 없이, 각 개발자가 필요에 따라 설정을 자유롭게 변경하며 작업할 수 있는 API 모킹 환경이 마련될 수 있었습니다.

프로젝트에 목 데이터를 저장하지 않고, 자신의 브라우저에 저장할 수도 있습니다.

그리고 UI를 통한 API 모킹 제어로 원하는 동작을 브라우저 내에서 즉시 반영할 수 있다 보니, 개발자에게도 즉각적으로 피드백을 줄 수 있는 다양한 기능들도 지원할 수 있게 되었습니다.

그 예시로는 실제 API 호출과 목 데이터 응답을 동시에 호출하는 "검증 모드"라 불리는 기능으로, MSW의 bypass 기능을 활용하여 실제 응답 값과 목 데이터를 비교하며 협의가 이뤄진 API 스펙에서 틀린 부분이 있을 시 개발자가 바로 인지할 수 있도록 안내해 줄 수 있었습니다.

그 결과 목 데이터가 최신화되지 않거나, 상황별 협의가 이뤄진 API 스펙이 틀릴 시 UI를 통해 빠르고, 직관적으로 인지할 수 있게 되었습니다.

실제 API 응답과 목 데이터의 인터페이스가 틀릴 시 스낵바를 통해 알려줍니다.

테스트 코드에서도 일관성 있는 API 모킹 환경 만들기

UI에서 MSW의 동작을 제어하며 앞선 문제들은 해결할 수 있었지만, 테스트 코드에서는 여전히 기존 MSW의 API 모킹 방식이 사용되면서 이전 문제들이 그대로 남아 있었습니다.

UI와 테스트 코드에서의 목 데이터의 관리 포인트가 분리되면서 일관성을 유지하기 어려워졌고, 관리 포인트가 나누어짐에 따라 유지 및 보수 측면에서도 효율적이지 않다는 판단을 내렸습니다.

다음 예시 코드를 통해 각 환경(브라우저, 테스트 코드)에서 목 데이터를 API 모킹 동작에 지정하는 방법을 보도록 하겠습니다.

Mock Service GUI에서 API 모킹 사용 시 목 데이터 사용 예시

import { extendHandlers, http } from '@core-web/mock-service-gui';
import { HttpResponse } from 'msw'
// 목 데이터 import는 생략

// 아래와 같이 상황에 맞는 목 데이터들을 API에 매핑시켜두면 UI를 통해 표기되며, UI를 통해 모킹된 API 응답 값을 실시간으로 교체할 수 있습니다.
export const membershipPaymentHandlers = extendHandlers(
    http.get('/membership/example', () => HttpResponse.json(fetchPaymentHistory_200)).
        presets({
            label: '200 - 결제 내역 조회 성공',
            status: 200,
            response: fetchPaymentHistory_200,
        },
        {
            label: '결제 내역 조회 실패 - 인증 실패',
            status: 401,
            response: fetch_401,
        },
        {
            label: '결제 내역 조회 실패 - 해지 회원',
            status: 400,
            response: fetch_400_UnsubscribeMember,
        },
));

브라우저 환경에서는 위 예시 코드와 같이 기존 MSW 핸들러에 체이닝된 presets 함수를 통해 연관된 목 데이터들을 매핑할 수 있습니다.

매핑된 목 데이터는 UI에 API와 연결된 프리셋 목록에 입력한 label 값을 기준으로 표시되며, 이를 통해 API 응답 값을 실시간으로 변경할 수 있습니다.

MSW에서 API 모킹 사용 시 테스트 코드 활용 예시

import { http, HttpResponse } from 'msw';
import { server } from '@mocks/server';
import fetctPaymentHistory_200_NextPageUsed from '@mocks/payment/response/(생략).json';

it('결제 내역에서 5개 이상의 아이템이 나올 시 더 보기 버튼이 노출된다', () => {
    server.use(
        http.get('/membership/example', () => {
            return HttpResponse.json(fetctPaymentHistory_200_NextPageUsed);
        }
    ),
    // 테스트 코드 생략
})

위 코드를 보시면 브라우저에서는 label 값을 사용해 모킹된 API의 목 데이터를 교체하지만, 테스트 코드에서는 목 데이터 JSON 파일을 불러와 API 모킹에 바로 적용하고 있어 서로 사용하는 방법이 다른 것을 볼 수 있습니다.

그래서 만약 테스트 환경에서도 UI와 유사한 방식으로 모킹을 제어하고 목 데이터의 관리 위치를 통합할 수 있다면, 앞서 언급하였던 문제들을 테스트 코드에서도 해결할 수 있을 것으로 생각하였습니다.

이를 위해 브라우저에서의 UI 사용성과 관리 방식을 테스트 코드에서도 일관되게 제공하고자 했고, 여러 해결 방안을 고민하던 끝에 사내 프론트엔드 기본 기술 스택으로 모든 프로젝트에 적용되어 있는 타입스크립트를 활용하는 방법을 시도해 보았습니다.

타입스크립트의 제네릭 확장을 활용하여 목 데이터 프리셋이 선언된 위치에서 리터럴 문자열 타입을 추출한 뒤, Extract 유틸리티 타입을 사용해 개발자가 입력하는 값에 따라 IDE 타입 서포트 기능을 통해 자동 완성 기능을 제공해 개발자 경험을 크게 향상시킬 수 있었습니다.

이러한 기능들 덕분에 목 데이터가 수정될 경우 타입 체크도 함께 이뤄져 테스트 코드 내 타입 에러를 사전에 방지할 수 있는 환경을 갖출 수 있었습니다.

선언된 MSW 핸들러를 가져와 useMock과 같이 추가로 제공되는 인터페이스를 통해 프로젝트에 선언된 API 모킹을 빠르게 탐색하고 테스트 코드에서 제어할 수 있습니다.

이를 기반으로 올해 팀 목표인 테스트 고도화와 커버리지 확대에 따라 테스트 코드 내 API 모킹 케이스가 증가하면서 테스트 코드에서도 Mock Service GUI의 활용도가 점차 높아졌고, 이러한 접근 방식을 통해 관리 포인트를 줄이고, 일관된 방식으로 테스트 코드에서 API 모킹을 제어할 수 있게 되었습니다.

그러나 앞선 문제들을 해결하였음에도, 이후 새로운 문제를 만나게 되었는데요.

바로 테스트 케이스가 증가함에 따라 각 테스트에 필요한 목 데이터가 과도하게 늘어나면서 실제 브라우저 디버깅 시 불필요한 목 데이터가 노출되고, UI에서 탐색이 불편해지는 문제가 발생했습니다.

이 문제를 해결하기 위해 Mock Service GUI는 override 기능을 추가했습니다.

이 기능은 이미 선언된 목 데이터의 일부 값만 수정하여, 테스트 시 API 모킹에 즉시 반영할 수 있도록 해줍니다.

위 사진과 같이 테스트 코드에서 새로운 목 데이터를 생성할 필요 없이, 기존 목 데이터의 일부 값만 즉시 수정하여 사용할 수 있으며
다음 예시와 같이 선언해둔 목 데이터의 인터페이스를 기반하여 타입 체크도 지원합니다.

이 기능을 통해 테스트에만 사용되던 불필요한 목 데이터들을 줄일 수 있었고, 각 상황에 필요한 목 데이터만 유지하는 간소화된 구조로 개선할 수 있었습니다.

또한, 부가적인 이점으로 override 기능은 컴포넌트 테스트에서도 유용하게 활용되었습니다.

기존 MSW의 API 모킹 방식으로는 컴포넌트가 어떤 API를 참조하는지까지는 표현할 수 있었지만, 실제로 참조하는 값을 명시적으로 표현하기는 어려웠습니다.

하지만, override 기능을 통해 컴포넌트가 참조하는 API 응답의 필드와 값을 그리고 상황에 대해 명시적으로 표현하는 테스트 코드를 작성할 수 있게 되었고, 이를 통해 테스트 코드의 가독성이 향상되었으며, 명세로서의 역할도 강화되었습니다.

기존 MSW만 사용하여 테스트 코드를 작성하였을 때

it('결제금액이 있는 결제 내역을 클릭하면 영수증 지면으로 이동된다.', async () => {
  server.use(
        http.get('/membership/example', () => {
            return HttpResponse.json(fetchPaymentHistroy_200_Once);
        }
    ),
  customRender(<PaymentHistoryContainer />);
  // 테스트 코드 생략
});

컴포넌트가 GET /membership/exampleAPI만 참조하는 것을 알 수 있습니다.

Mock Service GUI에서 제공하는 override 기능을 활용하여 테스트 코드를 작성하였을 때

it('결제금액이 있는 결제 내역을 클릭하면 영수증 지면으로 이동된다.', async () => {
  msgHandlers.useMock({
    method: 'GET',
    path: '/membership/example',
    preset: '결제 내역 조회 성공 - 결제한 기록이 있는 경우',
    override: ({ draft }) => {
      draft.data.payments[0].amount = 3990;
    },
  });

  customRender(<PaymentHistoryContainer />);
  // 테스트 코드 생략
});

컴포넌트가 GET /membership/exampleAPI에서 "결제 내역 조회 성공 – 결제한 기록이 있는 경우"의 상황에서
data.payments[0].amount 값을 참조하는 것을 알 수 있습니다.

Mock Service GUI 도입 이후

현재 Mock Service GUI는 팀 내에서 지속적으로 관리되고 있는 주문, 결제, 회원 도메인의 모든 프로젝트에 도입이 완료된 상태이며, 특히 올해 오픈한 배민클럽 과제의 경우 초기 프로젝트 셋업 과정부터 Mock Service GUI를 적극적으로 활용하여 아래처럼 수많은 목 데이터를 편리하게 사용하고 관리할 수 있었습니다.

또한, Mock Service GUI 2.0 버전이 도입된 이후, 앞서 소개해 드린 override 기능을 통해 테스트 환경에서 사용성이 크게 증가하어 배민클럽 구독 동선 프로젝트에서 높은 테스트 커버리지를 유지할 수 있었습니다.

그리고 올해 9월부터는 Mock Service GUI 2.0 버전이 사내에 정식 공개되어 다른 팀에서도 도입하고 있어 점진적으로 사용처가 확대되고 있습니다.

사내에서 제공 중인 Mock Service GUI 가이드 문서 사이트

끝맺음

작년 WOOWACON 2023 발표 이후, 많은 분께서 Mock Service GUI의 오픈 소스 전환 및 외부 공개 계획에 대해 문의해 주셨습니다.

여러분들의 기대에 부응하여(?) 현재 Mock Service GUI는 앞으로의 사내 도입 현황과 효과를 평가한 후, 오픈 소스로 전환하는 것을 목표로 하고 있습니다.

다양한 팀의 개발 환경에서 이 도구가 실제로 작업 효율성을 높이는 것을 확인하고, 외부에서도 프론트엔드 개발자들이 직면한 문제를 해결하는 유용한 도구가 되기를 기대하고 있으며

오픈 소스로 전환되면서 Mock Service GUI는 여러 개발자와의 협업을 통해 기능을 개선하고, 새로운 활용 방안을 찾아가며, API 모킹 환경에서 더욱 폭넓게 활용될 수 있도록 꾸준히 발전해 나가고자 합니다.

앞으로도 Mock Service GUI가 더 나은 개발 경험을 제공할 수 있도록 최선을 다하겠습니다.