[GraphQL] Apollo Client를 사용하는 React 컴포넌트 테스트하기 (MockedProvider)

Apollo Client를 사용하여 GraphQL API를 호출하는 React 컴포넌트를 테스트하는 방법에 대해서 알아보겠습니다.

예제 컴포넌트 작성

Apollo Client를 사용하여 GraphQL API를 호출하는 매우 간단한 React 컴포넌트를 작성해보겠습니다.
아래 PingPong 컴포넌트는 GraphQL 서버에 ping이라는 쿼리를 호출합니다.
서버로부터 응답이 올 때까지는 Loading...라는 메세지를 렌더링합니다.
만약에, 서버로 부터 에러가 응답되면 Error!라는 메세지를 렌더링합니다.
마지막으로 서버로 부터 정상적으로 데이터가 응답되면, 해당 데이터를 렌더링합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from "react";
import { gql } from "apollo-boost";
import { Query } from "react-apollo";

export const PING_QUERY = gql`
query ping {
ping
}
`;

function PingPong() {
return (
<Query query={PING_QUERY}>
{({ loading, error, data }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
return <p>{data.ping}</p>;
}}
</Query>
);
}

export default PingPong;

기존에 Apollo Client로 React 컴포넌트를 작성해본 적이 없으신 분들은 위 코드가 이해가 어려울 수도 있습니다.
아래 포스트에서 Apollo Client를 이용해서 React 앱 개발하는 방법에 대해서 설명하고 있으니 참고 바랍니다.

MockedProvider

Apollo Client를 사용하고 있는 React 컴포넌트에 대한 단위 테스트틀 작성할 때 가장 까다로운 부분이 실제로 GraphQL 서버와 연동이 필요하다는 점입니다.
상용 코드에서 사용하는 ApolloProviderclient prop에 ApolloClient 객체를 넘겨달라고 요구하는데, ApolloClient 객체를 생성할 때는 연동할 GraphQL 서버의 접속 정보를 설정해줘야 합니다.
이는 “단위 테스트는 다른 모듈에 의존하지 않고 격리되어 실행될 수 있어야 한다”는 기본 원칙에 위배되며, 실제로 단위 테스트 용 GraphQL 서버를 갖출 수 있다고 해도 테스트 실행 속도가 매우 느려질 것입니다.

1
2
3
4
5
6
7
8
9
10
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";

const client = new ApolloClient({
uri: "https://www.my-graphql-server.com"
});

<ApolloProvider client={client}>
<MyComponent />
</ApolloProvider>

해결 방법은 ApolloProvider를 모킹(mocking)하여 실제로 GraphQL 서버와 연동하는 것처럼 React 컴포넌트를 속여주는 것입니다.
이를 위해, Apollo Client의 React 용 라이브러리인 react-apolloMockedProvider를 제공해주고 있습니다.
MockedProviderApolloClient 객체를 필요로 하는 ApolloProvider와 달리 단지 가짜 데이터만을 필요로 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { MockedProvider } from "react-apollo/test-utils";

const mocks = [
{
request: {
query: ...,
variables: { ... },
},
result: {
data: { ... },
},
},
];

<MockedProvider mocks={mocks} addTypename={false}>
<MyComponent />
</MockedProvider>

각 요청에 대한 가짜 응답 결과를 담고 있는 mocks 배열을 MockedProvider에 넘겨주면 실제 GraphQL 서버가 없이도 MyComponent를 테스트할 수가 있습니다.

단위 테스트 작성

React 테스트를 작성할 때는 많이 사용되는 Jest와 React Testing Library를 이용해서 예제 컴포넌트에 대한 테스트를 작성해보겠습니다.

Jest와 React Testing Library에 대한 기본적인 설명은 다음 포스트를 참고 바랍니다.

먼저 서버로부터 응답이 올 때까지는 Loading...라는 메세지를 제대로 렌더링이 되는지 체크해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from "react";
import { MockedProvider } from "react-apollo/test-utils";
import { render, wait } from "@testing-library/react";

import PingPong, { PING_QUERY } from "./PingPong";

test("<PingPong/> renders loading while fetching data", () => {
const { getByText } = render(
<MockedProvider>
<PingPong />
</MockedProvider>
);

expect(getByText("Loading...")).toBeInTheDocument();
});

React Testing Library의 render() 함수를 이용해서 테스트할 PingPong 컴포넌트를 DOM에 렌더링해야합니다.
이 때, 위에서 설명드린 MockedProvider 컴포넌트로 PingPong 컴포넌트를 감싸줘야 합니다.
그 다음, React Testing Library의 getByText() 함수로 Loading... 메세지를 포함하고 있는 컴포넌트를 선택합니다.
마지막으로 jest-dom의 매처 함수인 toBeInTheDocument()를 이용해서 선택한 컴포넌트가 DOM 안에 존재하고 있는지 검증합니다.

다음으로 서버로 부터 에러가 응답되면 Error!라는 메세지가 제대로 렌더링이 되는지 체크해보겠습니다.

1
2
3
4
5
6
7
8
9
test("<PingPong/> renders error when error is responded", async () => {
const { getByText } = render(
<MockedProvider>
<PingPong />
</MockedProvider>
);

await wait(() => expect(getByText("Error!")).toBeInTheDocument());
});

MockedProvidermocks prop을 넘기지 않은 경우에는 항상 가짜 에러를 응답해줍니다.
여기서 중요한 것은, 화면에서 Error! 메세지가 나타날 때까지 기다려야 한다는 점입니다.
React Testing Library의 wait() 함수와 JavaScript의 async/await 키워드를 사용하여 비동기 처리를 해줍니다.

마지막으로 서버로 부터 정상적으로 데이터가 응답되면, 해당 데이터가 렌더링되는지를 체크해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
test("<PingPong/> renders data when data is responded", async () => {
const mocks = [
{
request: {
query: PING_QUERY
},
result: {
data: { ping: "pong" }
}
}
];
const { getByText } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<PingPong />
</MockedProvider>
);

await wait(() => expect(getByText("pong")).toBeInTheDocument());
});

이 번에는 PingPong 컴포넌트가 가짜 응답을 받아야 하기 때문에, MockedProvider 컴포넌트의 mocks prop에 ping 쿼리에 대한 가짜 응답을 담고 있는 배열을 넘겨줍니다.
대게의 경우 addTypename prop은 false로 세팅하는데, true 세팅해줄 경우, 요청 정보가 __typename 필드까지 포함해야 하기 때문입니다.

마치면서

지금까지 Apollo Client를 사용하여 GraphQL API를 호출하는 React 컴포넌트를 테스트하는 방법에 대해서 알아보겠습니다.
예제 프로젝트의 전체 코드는 아래를 통해 확인하실 수 있습니다.

공유하기