Logo

GraphQL 서버 테스트 방법 (Apollo Server Testing)

GraphQL 서버를 개발할 때도 여느 Rest API 서버와 마찬가지로 테스트의 중요성의 아무리 강조해도 지나치지 않습니다. 이번 포스팅에서는 Apollo Server로 개발된 GraphQL 서버에 대한 테스트를 작성하는 방법에 대해서 알아보도록 하겠습니다.

기존에 Apollo Server를 이용해서 GraphQL 서버를 개발하신 적이 없으신 분들은 관련 포스팅를 먼저 보시고 GraphQL 서버 프로젝트를 생성하시기 바랍니다.

프로젝트 설정

Apollo Server에서는 GraphQL 서버의 테스트를 돕기 위해 apollo-server-testing 패키지를 제공하고 있습니다. 따라서, 본인의 GraphQL 서버 프로젝트에 이 패키지를 설치하셔야 합니다.

$ npm i apollo-server-testing

참고로 해당 프로젝트에 apollo-servergraphql 패키지도 이미 설치되어 있어야 합니다.

테스트 대상 스키마 정의

테스트를 작성하려면 먼저 테스트 대상이 될 GraphQL 서버 API가 있어야 합니다. 여기서는 테스트 용으로 간단하게 유저를 조회하는 query와 유저를 삭제하는 mutation을 만들어 보겠습니다.

우선, typeDefs.js 파일을 생성 후, 유저의 데이터의 기반이 되는 User 타입을 정의합니다. 그리고 id를 인자로 받아 User 데이터를 리턴하는 findUserQuery 타입에 추가합니다. 그리고 id를 인자로 받아 true또는 false를 리턴하는 deleteUserMutation 타입에 추가합니다.

const { gql } = require("apollo-server");

module.exports = gql`
  type User {
    id: ID!
    name: String!
  }

  type Query {
    findUser(id: ID!): User
  }

  type Mutation {
    deleteUser(id: ID!): Boolean
  }
`;

테스트 대상 스키마 구현

위에서 정의한 타입 정보를 기준으로 테스트 대상이 되는 query와 mutation을 구현하겠습니다.

resolvers.js 파일을 생성 후, findUser query와 deleteUser mutation에 대한 구현 코드를 작성합니다. 여기서는 최대한 간단한 예제를 위해서 배열에 저장해 놓은 임의의 유저 데이터를 사용하겠습니다.

const users = [...Array(5).keys()].map((key) => ({
  id: key + "",
  name: `Name${key}`,
}));

module.exports = {
  Query: {
    findUser: (parent, { id }) => {
      const user = users.find((user) => user.id === id);
      if (user) {
        return user;
      } else {
        throw new Error("Not Found!");
      }
    },
  },

  Mutation: {
    deleteUser: (parent, { id }) => {
      const index = users.findIndex((user) => user.id === id);
      if (index < 0) return false;
      users.splice(index, 1);
      return true;
    },
  },
};

findUser의 구현 코드는 넘어온 id와 일치하는 유저 데이터를 배열에서 찾아서 리턴합니다. 만약 id와 일치하는 데이터가 없는 경우, 예외를 발생시킵니다. deleteUser의 구현 코드는 넘어온 id와 일치하는 유저 데이터를 찾아서 배열에서 찾아서 삭제 후 true를 리턴합니다. 만약 삭제할 데이터가 없는 경우, false를 리턴합니다.

테스트 코드 작성

 위에서 설치한 apollo-server-testing 패키지는 createTestClient라는 함수를 제공하는데, 이 함수를 이용하면 매우 간편하게 GraphQL 서버에 대한 테스트 코드를 작성할 수 있습니다. 먼저 위에서 작성한 typeDefs.js 파일과 resolvers.js 파일을 임포트 한 후에, 일반적인 Apollo Server 객체의 생성 방식과 동일하게 ApolloServer 생성자의 인자로 넘겨줍니다.

그 다음, createTestClient 함수의 인자로, 이 Apollo Server 객체를 넘겨주면, querymutate라는 테스트 전용 함수를 가진 테스트 서버 객체를 얻을 수 있습니다. 따라서, 복잡하게 HTTP 서버를 직접 구성하거나, Express와 같은 서버를 사용하지 않아도, 간단하게 GraphQL 서버에 대한 테스트 코드를 작성할 수 있습니다.

const { ApolloServer, gql } = require("apollo-server");
const { createTestClient } = require("apollo-server-testing");
const typeDefs = require("./typeDefs");
const resolvers = require("./resolvers");

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

const { query, mutate } = createTestClient(server);

Query 테스트

먼저 findUser query에 대한 테스트를 작성해보겠습니다.

첫 번째 테스트 시나리오는 유저가 정상적으로 조회되는 경우입니다. querymutate 함수는 모두 Promise를 리턴하는 함수이기 때문에, async/await 키워드를 사용하여 비동기 처리를 해주었습니다.

async/await 키워드에 대한 자세한 설명은 관련 포스팅를 참고바랍니다.

GraphQL query가 성공한 경우에는 응답 결과에 data 속성이 존재하기 때문에, 응답된 유저 정보를 읽어와서 검증할 수가 있습니다.

test("find user", async () => {
  const FIND_USER = gql`
    query {
      findUser(id: "1") {
        id
        name
      }
    }
  `;

  const {
    data: { findUser },
  } = await query({ query: FIND_USER });

  expect(findUser).toEqual({ id: "1", name: "Name1" });
});

findUser query의 두 번째 테스트 시나리오는 유저를 찾을 수 없어서 예외가 발생되는 경우입니다. GraphQL query가 실패한 경우에는 응답 결과에 errors 속성이 존재하기 때문에, 응답된 예외 정보를 읽어와서 검증할 수가 있습니다.

test("throw error if user is not found", async () => {
  const FIND_USER = gql`
    query {
      findUser(id: "10") {
        id
        name
      }
    }
  `;

  const {
    errors: [error],
  } = await await query({ query: FIND_USER });

  expect(error.message).toEqual("Not Found!");
});

Mutation 테스트

다음으로 deleteUser mutation에 대한 테스트를 작성해보겠습니다.

첫 번째 테스트 시나리오는 유저가 정상적으로 삭제되어 true가 응답되는 경우입니다. 이번에는 id를 변수를 빼내어 mutate 함수의 인자로 넘겨보았습니다.

test("delete user", async () => {
  const DELETE_USER = gql`
    mutation ($id: ID!) {
      deleteUser(id: $id)
    }
  `;

  const {
    data: { deleteUser },
  } = await mutate({ mutation: DELETE_USER, variables: { id: "1" } });

  expect(deleteUser).toBeTruthy();
});

두 번째 테스트 시나리오는 유저가 이미 삭제되어 삭제가 실패하여 false가 응답되는 경우입니다.

test("can not delete user twice", async () => {
  const DELETE_USER = gql`
    mutation ($id: ID!) {
      deleteUser(id: $id)
    }
  `;

  const {
    data: { deleteUser },
  } = await mutate({ mutation: DELETE_USER, variables: { id: "1" } });

  expect(deleteUser).toBeFalsy();
});

테스트 실행

본 포스팅의 예제 코드에서는 테스팅 프레임워크로 jest를 사용하였습니다.

테스팅 프레임워크인 Jest에 대한 기본적인 설정 방법은 관련 포스팅을 참고 바랍니다.

다음과 같이 jest를 이용해서 테스트를 실행해주면, 그 동안 작성한 모든 테스트가 통과하는 것을 알 수 있습니다.

$ yarn test
yarn run v1.16.0
$ jest
 PASS  ./test.js
  ✓ find user (10ms)
  ✓ throw error if user is not found (24ms)
  ✓ delete user (2ms)
  ✓ can not delete user twice (1ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        6.739s
Ran all test suites.
Done in 12.22s.

마치면서

지금까지 Apollo Server 기반 GraphQL 서버를 어떻게 테스트하는지에 대해서 알아보았습니다. 예제 프로젝트의 전체 코드는 아래를 통해 확인하실 수 있습니다.

관련 포스팅