Table of Contents
Apollo Serverでモックを作る
まず、apollo-serverをインストールします。
$ npm install apollo-server
次に簡単なスキーマを書いてモックサーバーを用意します。
// index.js
const { ApolloServer, gql } = require("apollo-server");
const typeDefs = gql`
type Query {
todos: [Todo]
}
type Todo {
id: ID!
title: String
body: String
done: Boolean
}
`;
const server = new ApolloServer({
typeDefs,
mocks: true
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
localhost:4000
にアクセスするとPlaygroundが立ち上がるので試しにクエリを実行してみます。
{
todos {
id
title
body
done
}
}
すると以下のような結果が返ってきます。
{
"data": {
"todos": [
{
"id": "10bade2d-ffc2-43ce-9bb4-597a475b0fd8",
"title": "Hello World",
"body": "Hello World",
"done": true
},
{
"id": "1e06c063-1944-4493-bb96-abb32e216cb2",
"title": "Hello World",
"body": "Hello World",
"done": true
}
]
}
}
モックをカスタマイズする
今回はテストで期待する値が返ってきて欲しいので以下のようにカスタマイズします。
const { ApolloServer, gql } = require("apollo-server");
const typeDefs = gql`
type Query {
todos: [Todo]
}
type Todo {
id: ID!
title: String
body: String
done: Boolean
}
`;
const resolvers = {
Query: {
todos: () => {
return [
{
id: 1,
title: "My Todo 1",
body: "読書をする",
done: false
},
{
id: 2,
title: "My Todo 2",
body: "ジョギングをする",
done: false
}
];
}
}
};
const server = new ApolloServer({
typeDefs,
resolvers
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
クエリを用意する
$ npm install graphql-tag
// graphql/todo.js
const gql = require("graphql-tag");
module.exports.GET_TODOS = gql`
query GetTodos {
todos {
id
title
body
done
}
}
`;
クライアント側で利用するクエリを用意しました。
これらを apollo-client
やフレームワークを使うなら react-apollo
、 vue-apollo
などと一緒に使っていことになるはずです。
例えば、 vue-apollo
を使った場合だと
// TodoList.vue
<template>
<div>
<ul>
<li v-for="todo in todos" :key="todo.id">{{ todo.title }}</li>
</ul>
</div>
</template>
<script>
import { GET_TODOS } from './graphql/todo'
export default {
name: "TodoList",
apollo: {
todos: {
query: GET_TODOS,
}
},
data () {
return {
todos: []
}
}
};
</script>
<style scoped>
</style>
このように読み込んでデータ取得表示できます。
テストを書いてみる
$ npm install jest apollo-server-testing
テストでApolloServerインスタンスが必要になるため、別ファイルに切り離しておきます。
// apollo-server.js
const { ApolloServer, gql } = require("apollo-server");
const typeDefs = gql`
type Query {
todos: [Todo]
}
type Todo {
id: ID!
title: String
body: String
done: Boolean
}
`;
const resolvers = {
Query: {
todos: () => {
return [
{
id: 1,
title: "My Todo 1",
body: "読書をする",
done: false
},
{
id: 2,
title: "My Todo 2",
body: "ジョギングをする",
done: false
}
];
}
}
};
const server = new ApolloServer({
typeDefs,
resolvers
});
module.exports.server = server;
// index.js
const { server } = require("./apollo-server");
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
テストの準備が整ったので、Jestを使って試しにスナップショットテストを書いてみます。
// tests/todo.spec.js
const { createTestClient } = require("apollo-server-testing");
const { server } = require("../apollo-server");
const { GET_TODOS } = require("../graphql/todo");
describe("My Todo Test", () => {
it("gets todos", async () => {
const { query } = createTestClient(server);
const res = await query({ query: GET_TODOS });
expect(res).toMatchSnapshot();
});
});
以下のような結果になりました。
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`My Todo Test gets todos 1`] = `
Object {
"data": Object {
"todos": Array [
Object {
"body": "読書をする",
"done": false,
"id": "1",
"title": "My Todo 1",
},
Object {
"body": "ジョギングをする",
"done": false,
"id": "2",
"title": "My Todo 2",
},
],
},
"errors": undefined,
"extensions": undefined,
"http": Object {
"headers": Headers {
Symbol(map): Object {},
},
},
}
`;
Introspectionを使ったモック
一通りApollo Serverとクエリの統合テストを行ってみました。
しかし、GraphQLサーバーアプリケーションがNode.jsで作られたものではない場合(例えばgraphql-rubyやgraphql-phpを使ったものなど)、クライアント側のクエリがバックエンドで定義されているスキーマと合っているかテストするのは難しいです。
そこで、Apollo Serverには Introspection を使ってモックサーバーを立てることが可能です。
Introspectionクエリはバックエンド側で定義されたGraphQLのスキーマ情報を取得できるクエリで、その結果をApollo Serverに読み込ませ、モックサーバーとして動かすことでクライアント側クエリのテストを実現できます。
Apollo CLIを使ってIntrospectionクエリ結果を出力
Apollo CLIを使うことでGraphQLサーバーのエンドポイントからIntrospectionクエリの結果をjsonファイルとして出力します。
まず、Apollo CLIをグローバルにインストールします。
$ npm install -g apollo
今回はNode.jsアプリケーションですが、別言語でのアプリケーションが localhost:4000
のエンドポイントにサーバーが動いていると仮定して以下コマンドで出力できます。
$ apollo client:download-schema --endpoint=http://localhost:4000 schema.json
スキーマ情報が書かれているschema.jsonが生成されます。
スキーマを基にモックサーバーを用意する
スキーマ情報をNode.jsでも扱えるjsonファイルにすることができました。
これを graphql
パッケージの buildClientSchema()
を使ってGraphQLSchemaオブジェクト生成、それをApollo Serverオプションとして読み込ませることで、Node.jsで完結したGraphQLサーバーのモックが完成します。
それではapllo-server.jsをschema.jsonを読み込む形に編集してみます。
const { ApolloServer } = require("apollo-server");
const { buildClientSchema } = require("graphql");
const introspectionResult = require("./schema.json");
const schema = buildClientSchema(introspectionResult);
const server = new ApolloServer({
schema,
mocks: true
});
module.exports.server = server;
この状態でサーバーを起動すると、最初に動かしたような"Hello World" の文字列などが返ってくるレスポンスになっていると思います。
今回はクライアント側のクエリがスキーマに対して正しいかどうかチェックできればいいので、mocksオプションから型ごとに固定の値を返すことができます。
const mocks = {
ID: () => 12,
Int: () => 123,
Float: () => 123.456,
String: () => "テスト"
};
const server = new ApolloServer({
schema,
mocks
});
クエリの実行結果は以下のようになります。
{
"data": {
"todos": [
{
"id": "12",
"title": "テスト",
"body": "テスト",
"done": false
},
{
"id": "12",
"title": "テスト",
"body": "テスト",
"done": false
}
]
}
}
これで apollo-server-testing
と Jest
でテストを書くと、何かしらバックエンド側でスキーマが変更あったなどして、クライアント側で使っているクエリとで齟齬があった場合に気づくことができます。
まとめ
まず最初に、フロントエンドはApollo Clientで、バックエンドがNode.js(Apollo Server)の構成での apollo-server-testing
とJest
を使った統合テストを行ってみました。
次に、フロントエンドとしてApollo Client、バックエンドでRubyやPHPなどの別言語を想定した場合のクエリテストを行いました。
こういったテストを行うことでクライアントアプリで投げているGraphQLクエリが(少なくともAPIレベルでは)サーバー側とコミュニケーションが正確にとれていること担保できます。
おまけ: ESLintでのクエリチェック
実はすでに eslint-plugin-graphql というものがあり、ESLintでスキーマに対しての.gqlや.jsファイルで書かれたクエリのチェックは可能です。
こちらの方がエディタのESLint拡張と組み合わせることで編集時にエラーが出たり、補完が効いたりと便利です。
しかし、これはあくまでLinterなので書かれているクエリ情報までしかチェックできず、MutationでのInputタイプの中まではチェックされません。
例えば、
input TodoInput {
title: String
body: String
}
type Todo {
id: ID!
title: String
body: String
}
type Mutation {
createTodo(input: TodoInput): Todo
}
とバックエンド側でスキーマが定義されていた場合、
クライアント側では、
mutation CreateTodo($input: TodoInput) {
createTodo(input: $input) {
id
title
body
}
}
といったクエリが投げられたとします。
この時に渡ってくる variables
が以下だったとします。
{
"title": "読書",
"content": "一時間読書をする"
}
この場合 title
と body
を渡さないといけないですが、content
という間違った値が渡された場合はGraphQLエラーになります。
これはESLintではチェックできません。
そこで今回行ったようにモックのApollo Serverを用意し、そこに対してクエリを投げてやることで variables
までを考慮されるクエリがパースされた後のものでチェックが入ります。
そのため、シンプルなQuery/MutationはESLintでチェックし、複雑なInputタイプを持つMutationはしっかりテストを書いておくのがいいかもしれません。