개요
백엔드를 구현하기 전에 프론트에서 구현을 위해 필요한 정보를 Mock 데이터를 생성해 제공받아서 사용하려고 생각했고 바로 실행해 옮겼습니다.
- 3개의 미리 생성된 요약카드 구현
- 검색 후 검색한 소환사명의 데이터로 만들어진 완성된 카드 구현
- 다시 홈으로 돌아가면 최근 생성한 카드가 맨 앞에 나오도록 구현
이 3가지 동작을 구현하기 위한 Mock 데이터를 구현해야 했습니다.
구현
(Main의 요약카드 데이터 쿼리)
const GET_CARD_DATA = gql`
query GetCardData($name: String) {
summoner(name: $name) {
id
information {
...CardTitleFragment
}
}
}
${CardTitleFragment}
`;
(SummonerCard 컴포넌트의 완성된 카드 데이터를 불러오는 쿼리)
export const GET_CARD_DATA = gql`
query GetCardData($name: String) {
summoner(name: $name) {
id
information {
__typename
...CardTitleFragment
}
season
tendency
lanes
champions {
name
winRate
gamesPlayed
KDA
}
}
}
${CardTitleFragment}
`;
Apollo, GraphQL의 공식문서로 진행하다보니 Mock도 자연스럽게 @graphql-tools를 사용해서 진행했습니다.
구현하면서 검색하는 도중에 MSW에 대한 내용도 찾아봐서 여유만 되면 MSW로도 진행해보고 싶네요.
Apollo 공식문서, @graphql-tools 문서의 Mocking 방법이 조금 차이가 있어서 두 문서 모두 보면서 구현했습니다.
스키마 구현
export const typeDefs = `
type Information {
summonerName: String!
summonerLevel: Int!
summonerIcon: String!
}
type Champion {
name: String!
winRate: Float!
gamesPlayed: Int!
KDA: Float!
}
type Summoner {
id: Int!
information: Information!
season: [String!]!
tendency: [String!]!
lanes: [String!]!
champions: [Champion!]!
}
type Query {
summoner(name: String): [Summoner!]!
}
`;
소환사와 관련된 정보를 조회하기 위한 스키마를 구현합니다.
Mock 데이터 설정
export const mocks = {
Query: () => ({
summoner: () => mockData
})
};
export const mockData = [
{
id: 1,
information: {
summonerName: 'test33',
summonerLevel: 333,
summonerIcon: '4123'
},
season: ['2012', '2013', '2014', '2015', '2016', '2023'],
tendency: ['갱킹선호', '초중반지향', '캐리형'],
lanes: ['JUNGLE', 'UTILITY'],
champions: [
{ name: 'Shaco', winRate: 72.5, gamesPlayed: 100, KDA: 3.5 },
{ name: 'Heimerdinger', winRate: 60.0, gamesPlayed: 150, KDA: 4.0 },
{ name: 'Ivern', winRate: 50.0, gamesPlayed: 200, KDA: 2.5 }
]
},
// 2번째와 3번째는 생략
];
테스트할 Mock 데이터를 생성합니다.
리졸버 설정
const resolvers = {
Query: {
summoner: (_: any, args: SummonerArgs) => {
if (
args.name === '말비나33' &&
!globalData.some(data => data.id === searchedData.id)
) {
globalData = [searchedData, ...globalData];
}
return globalData;
}
}
};
소환사명으로 검색하면 해당 소환사에 맞는 정보를 반환해줘야하기 때문에 '말비나33' 으로 검색할 때 데이터가 기존 MockData에 추가되는 리졸버를 구현했습니다.
Mocking 설정
makeExecutableSchema({ typeDefs, resolvers })
으로 스키마와 리졸버 함수를 조합하고
addMockSToSchema
함수를 사용해 스키마에 mocking 데이터를 추가합니다.
addMocksToSchema({
schema: makeExecutableSchema({ typeDefs, resolvers }),
mocks,
preserveResolvers: true
//해당 옵션으로 리졸버 로직과 mock 데이터가 함께 동작합니다.
})
const server = new ApolloServer({
schema: addMocksToSchema({
schema: makeExecutableSchema({ typeDefs, resolvers }),
mocks,
preserveResolvers: true,
}),
});
마지막으로 ApolloServer을 사용해 생성된 스키마를 사용해 서버를 생성합니다.
Next.js와 통합을 위해 startServerAndCreateNextHandler
함수를 사용해서 핸들러를 생성하지만 이 내용은 Mock 포스팅이 아니라 App Router을 사용한 요청 처리 부분에서 작성하겠습니다.
이렇게 테스트를 위한 Mocking을 구현했고 성공적으로 데이터를 받아 프론트 구현을 진행하고 있습니다.
고민했던 부분
소환사명을 검색하면 그 소환사의 정보를 반환하는 과정을 테스트하기위해 임의의 소환사명(말비나33)의 데이터를 설정하고 "말비나33"으로 검색했을 때 해당 데이터의 카드정보가 나오도록 구현했습니다.
이를 위해서 우선 메인페이지에서 소환사를 검색 시 카드페이지에 해당 소환사명을 파라미터로 url에 포함시켜 Card 페이지 컴포넌트에서 해당 키워드를 추출해 사용했습니다.
const searchParams = useSearchParams();
const summonerName = searchParams.get('summonerName') as string;
next/navigatioin
의 useSearchParams
를 사용했습니다.
리졸버를 사용해서 summoner 필드에 대해 args.name
의 값이 '말비나33'인 경우 mock 데이터에 '말비나33'의 정보가 포함되어 있지 않으면 맨 앞에 추가 하도록 구현했습니다.
sumonner에서 카드 요약 정보가 들어갈 information 부분은 중복되는거 같아서
fragment로 분리했는데 클라이언트에 값이 들어갈 때 fragment 부분만 데이터가 안가졌습니다.
서버에서 로그를 찍어보면 잘 반환되는데 클라이언트 쪽에서만 데이터가 빈 배열이 들어가더라고요.
구글링 해본결과 \_\_typename
으로 받을 객체의 타입을 확실히 해줘야 하는거 같습니다.
해당 부분을 쿼리에 추가해서 해결했습니다.
Stack Overflow: Apollo Client Fragments Not Embedding Data
참고한 내용
프론트에서 mocking에 대해 다룬 블로그
Mocking FE at Kakao Tech
mocking 구현 공식문서 Apollo Server Testing and Mocking GraphQL Tools MockServer GraphQL Tools Issues