こんにちは!
2020年は、まさにgraphql元年と言える年となり、私たちは多くのgraphql開発プロジェクトのご支援をさせて頂きました。
2021年も引き続き、graphqlの大衆化を目指して活動して参りますので、どうぞよろしくお願いいたします。
本記事では、AWS AppSyncのgraphql開発で必ず登場するVTLのテスト方法をご紹介します。
結論から言うと、VTLコードに対して直接的な単体テストは書けないです。
正確には、VTL言語の動作するサーバーインスタンスをDockerなどで起動し、構文チェック程度ならできる可能性ありますが、AppSyncで動作するVTLは少々特別な挙動がいくつかありますので、graphqlに対応した本質的なテストは行えません。
このことから、私たちは通常graphqlに対し下記のようなテストを行います。
jestなどのテストフレームを使用しAppSyncへ直接テストを行います。ローカルでAppSyncのサーバーを起動してテストしない理由は後述いたします。
AWSの公式に「NodeJS クライアントアプリを作成する」という記事がありますので、こちらを参考にローカルのNodeJSでAppSyncClientを作成します。
// __tests__/appSyncUtils
// jest上でのAppSyncClientサンプル(認証タイプ=CognitoUserPool)
import AWSAppSyncClient, {AUTH_TYPE} from 'aws-appsync';
require('isomorphic-fetch');
const getAppSyncClient = () => {
const client = new AWSAppSyncClient({
url: {AppSyncエンドポイント},
region: 'ap-northeast-1',
auth: {
type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
jwtToken: {JWTトークン}
},
disableOffline: true
});
client.defaultOptions = {
watchQuery: {fetchPolicy: 'no-cache'},
mutate: {fetchPolicy: 'no-cache'},
query: {fetchPolicy: 'no-cache'}
};
return client;
};
次は、graphqlコードを用意し実際にリクエストを行います。リクエスト後、AWS SDKを使用し実際にDynamDBの値が更新されているかどうか評価します。
// __tests__/createTodo.spec.js
import gql from 'graphql-tag';
import _ from 'lodash';
import {createTodo} from '../../graphql/mutations';
import {getItem, deleteItem} from '../dynamodbutil';
import {getAppSyncClient} from '../appSyncUtils';
const TABLE_NAME = 'Todo';
describe('AppSync : createTodo', () => {
const defaultQuery = gql(createTodo);
const adminUser = getAdminUser();
const appSyncAdminClient = getAppSyncClient(adminUser);
let responseId
beforeEach(async () => {
responseId = []
})
describe('正常系', () => {
test('成功すること', async () => {
const input = {
Name: "test todo"
}
const fetchResult = await appSyncAdminClient.mutate({
mutation: defaultQuery,
variables: {
input: input
}
});
const response = fetchResult.data.createTodo
responseId.push(response.Id)
// 引数で指定した値がレスポンスされていること
expect(response.Name).toEqual(input.Name);
// DynamoDBにItemがPUTされていること+属性値が正しいこと
const dynamoResult = await getItem(TABLE_NAME, {
Id: response.Id
})
// 評価に不要なキーを取り除く
delete response.__typename
expect(dynamoResult.Item).toStrictEqual(response);
});
})
afterAll(async () => {
// 作成したデータの削除
_.forEach(responseId, id => {
deleteItem(TABLE_NAME, {
Id: id
})
})
});
});
AppSyncにリクエスト直後のAWS SDKによるDynamoDBからのデータ取得は、DynamoDBに更新内容が反映されていない可能性があるので getItemにはConsistentRead
を忘れずに設定しておきましょう。
また、graphqlコードはamplifyで自動的に指定のAppSyncから取得可能ですので、合わせてこちらも実行しておきましょう。
amplify add codegen --apiId {appsync_id}
ローカルでAppSyncを動作を完全にシミュレーションしてくれるDockerイメージ、またはServerlessFrameworkを始めとした便利ツールが存在しないためです。
私たちはローカルでのAppSyncシミュレーション方法について、幾度も検証に取り組んできましたが、例えばVTLでのnull値に対する挙動が微妙に実際のAppSyncサーバーと異なるなど、その微妙な違いに大変苦しめられました。(ローカルでのテストを優先するかどうかの議論は本末転倒なので致しません)
結果、私たちは開発中のAWS費用が高くついてでも、直接AppSyncへテストを行うようにしています。プロジェクト全体を考えるのであれば、ここで開発効率を大きく落とすのは、開発コスト・速度の両面でデメリットの方が大きくなってしまう為です。
こちらは直接データソースに対して単体テストを記述する方法です。よくあるのは、Lambdaなどへ単体試験を実行するケースです。
Lambdaに対する単体テストコードは、基本的にローカルで完結できるように、serverlessFrameworkやDockerなどでローカル環境を再現しつつ、場合によってはjestのspyOn
によるMockも活用しましょう。
// jestによるAWS SDKのMock化サンプル
import * as AWS from 'aws-sdk';
jest
.spyOn(
AWS.CognitoIdentityServiceProvider.services['2016-04-18'].prototype,
'adminCreateUser'
)
.mockImplementation((request) => {
// リクエストに対する処理を書いてスタブ化することも可能
// returnするオブジェクトはMock化する対象の関数によって変わるので注意
return { promise: () => 'ok' };
});
CIなどでテストを自動化する場合、AWS SDKをテストコード内でスタブ化するなどの考慮が必要になることがありますので、適宜テストフレームワークを活用して対応しましょう。私たちはNodeJSでテストを書くことが多いためJestで実現することが多数です。
graphqlでの開発・テストによる堅牢なシステム開発はお気軽に、お問い合わせください。