적는다 해놓고 아직 완성하지 못한 글들이 많네요..ㅎㅎㅠㅠ 회고는 돌아오는 주 내에 꼭 작성할려고 합니다..! 회사에서 신기능을 연달아 배포하면서 시간이 많이 부족했었네요..하핳

어쨌든 지금 적을려고 하는 것은..! Apollo Client에서 실제 API 엔드포인트에 연결하여 사용하는 것처럼 Mocking하여 작업하는 방법입니다!


기능을 구현하는 과정에서 백엔드가 아직 완성되지 않았다면 보통 간단한 목 데이터를 만들어 UI적인 기능을 완성시켜 놓고, 백엔드가 구현되면 연결을 하는 방식으로 진행합니다. 아닌 경우도 있겠지만 대부분 이렇게 진행하는 것으로 알고 있습니다. 여기서 MSW를 이용해 API에서 데이터를 받는 것처럼 구현할 수도 있죠..! 해당 작업은 Apollo Client에서 MSW를 사용하는 것처럼 API를 통해 데이터를 받아 UI를 구현할 수 있는 Mocking 환경을 만드는 작업입니다. 이 작업을 진행하는 환경은 다음과 같습니다.

  • 그래프큐엘 쿼리 및 뮤테이션을 프론트엔드에서 구성
  • 그래프큐엘 코드젠을 이용해 쿼리 및 뮤테이션 기반 useQuery 훅과 useMutation 훅을 생성
  • 백엔드 측에서 API 구현 전 그래프큐엘 스키마를 제일 먼저 구현

 

이 과정에서 Mocking 하는 방법을 러프하게 설명하자면, 

  1. 백엔드로부터 생성하려고 하는 그래프큐엘 스키마를 전달 받음
  2. 해당 스키마를 mocks 폴더에 gql 파일로 생성
  3. 해당 gql 파일이 존재하는 경로를 기존 코드젠 코드가 참조하는 스키마 옵션에 추가
  4. faker.js와 같은 라이브러리를 이용해 사용하려고 하는 데이터 타입에 맞춰 목데이터 코드 구현
  5. ApolloLink를 이용해 API 요청을 가로채서 목데이터를 반환해주는 코드 구현
  6. 아폴로 클라이언트를 생성하는 함수에서 Mocking 하려는 쿼리 혹은 뮤테이션 타입 이름 기준으로 분기처리
    -> Mocking 하려는 쿼리 혹은 뮤테이션일 경우,
    -> 목데이터를 반환하는 Mocking 용 Apollo Link를 사용

위와 같습니다. 해당 방법으로 코드를 구현하면 백엔드에서는 아직 구현되지 않았더라도 구현된 것처럼 사용할 수 있습니다. 그리고 백엔드에서 구현했다면 코드젠에서 mocks의 스키마를 참조하지 않도록 바꾸고, 아폴로 클라이언트를 생성하는 함수에서의 분기처리를 무효화하면 됩니다.

 

1.  Mocking용 스키마와 쿼리 파일 생성

 

 

2. faker.js를 이용한 mock 데이터 구현

const createRandomData = (index: number): CoverLetter => {
  const yearsOfExperience = faker.number.int({ min: 1, max: 5 });

  return {
    __typename: "{Mocking 하는 쿼이 데이터 타입}",
    id: String(index + 1),
	//...
    university: `${faker.location.city()}대학교`,
  };
};

export const MOCK_DATA: {Mocking 하는 쿼이 데이터 타입}[] = Array.from(
  { length: 100 },
  (_, index) => createRandomData(index)
);

 

 

3. Mocking 하려는 쿼리에 대한 작업을 해주는 ApolloLink 생성 함수 

import { MOCK_DATA } from "../mocks/mock-cover-letter";
import { ApolloLink, Observable, Operation } from "@apollo/client";

export const createMockLink = () => {
  return new ApolloLink((operation: Operation) => {
    const { operationName, variables } = operation;

    return new Observable(observer => {
      if (operationName === "{Mocking 쿼리 타입 1}") {
        console.log("✅ Mocking with variables:", variables);
        
        let results = [...MOCK_DATA];

        if (variables?.keyword) {
          const lowerKeyword = variables.keyword.toLowerCase();
          results = results.filter(content =>
            content.toLowerCase().includes(lowerKeyword)
          );
        }
        if (variables?.companyName) {
          const lowerCompanyName = variables.companyName.toLowerCase();
          results = results.filter(content =>
            content.companyName.toLowerCase().includes(lowerCompanyName)
          );
        }

        const page = pagination?.page ?? 1;
        const pageSize = pagination?.pageSize ?? 10;
        const offset = (page - 1) * pageSize;
        const paginatedNodes = results.slice(offset, offset + pageSize);

        observer.next({
          data: {
            {Mocking 쿼리에 대한 반환 데이터 1}: {
              __typename: "{반환 데이터에 대한 타입 1}",
              nodes: paginatedNodes,
              totalCount: results.length,
            },
          },
        });
      } else if (operationName === "{반환 데이터에 대한 타입 2}") {
        console.log("✅ Mocking with variables:", variables);
        const found = MOCK_DATA.find(c => c.id === variables.id);

        observer.next({
          data: {
           {Mocking 쿼리에 대한 반환 데이터 2}: found || null,
          },
        });
      }

      observer.complete();
    });
  });
};

 

 

4. Apollo Client 생성 함수에서 Mocking 하려는 쿼리에 대한 분기 처리 코드 추가
(ApolloLink.split 활용)

const createApolloClient = (accessToken: Token, refreshToken: Token) => {
  //...
 
  const realHttpLink = ApolloLink.from([authLink, errorLink, httpLink]);

  const MOCKED_OPERATIONS = ["{Mocking 쿼리 타입 1}", "{Mocking 쿼리 타입 2}"];

  const splitLink = ApolloLink.split(
    operation => {
      const definition = getMainDefinition(operation.query);

      if (definition.kind === "OperationDefinition" && definition.name) {
        return MOCKED_OPERATIONS.includes(definition.name.value);
      }

      return false;
    },
    createMockLink(),
    realHttpLink
  );

  return new ApolloClient({
    link: splitLink,
    //...
  });
};

 

  • ApolloLink.split의 역할은 다음과 같습니다.
    • ApolloLink.split(test, left, right);
    • test: boolean 값을 반환하는 함수입니다. 들어오는 모든 GraphQL 요청(operation)에 대해 이 함수가 실행됩니다.
    • left: test 함수가 true를 반환했을 때 요청을 보낼 Link입니다. (이 코드에서는 createMockLink())
    • right: test 함수가 false를 반환했을 때 요청을 보낼 Link입니다. (이 코드에서는 realHttpLink)
  • 즉, 정의가 가능한 Operation이고, 이름이 있는지 확인한 후, Operation이 MOCKED_OPERATIONS 배열 내부의 값인지 확인하여
if (definition.kind === "OperationDefinition" && definition.name) { 
    return MOCKED_OPERATIONS.includes(definition.name.value); 
}
  • 맞다면 createMockLink()를 실행하여 Mock 데이터를 보내주고, 아니라면 실제 백엔드에 요청하여 실제 데이터를 가져옵니다.

 

 

5. 코드젠의 schema 옵션에 배열 형태로 Mocking용 스키마 파일을 추가

import { CodegenConfig } from "@graphql-codegen/cli";

const codegenConfig: CodegenConfig = {
  //...
  schema: [
    //...
    "src/lib/graphql/mocks/develop.schema.graphql",
  ],
  //...
};

export default codegenConfig;

 

 

이렇게 진행하면 아직 구현되지 않은 백엔드 API를 Mocking하여 사용할 수 있습니다!

긴 글 읽어주셔서 감사합니다 :)

+ Recent posts