AppSyncのVTLのテスト方法は?graphql開発エキスパートがVTLのテスト方法を伝授します😎

AppSyncのVTLのテスト方法は?graphql開発エキスパートがVTLのテスト方法を伝授します😎

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは!

2020年は、まさにgraphql元年と言える年となり、私たちは多くのgraphql開発プロジェクトのご支援をさせて頂きました。

2021年も引き続き、graphqlの大衆化を目指して活動して参りますので、どうぞよろしくお願いいたします。

本記事では、AWS AppSyncのgraphql開発で必ず登場するVTLのテスト方法をご紹介します。

想定する読者

  • これからgraphql開発を始めるヒト
  • 新しくgraphql開発プロジェクトを立ち上げるヒト
  • RestAPIはわかるけどgraphqlを触るのは初めてのヒト

VTLのテスト方法の概要

結論から言うと、VTLコードに対して直接的な単体テストは書けないです。
正確には、VTL言語の動作するサーバーインスタンスをDockerなどで起動し、構文チェック程度ならできる可能性ありますが、AppSyncで動作するVTLは少々特別な挙動がいくつかありますので、graphqlに対応した本質的なテストは行えません。

このことから、私たちは通常graphqlに対し下記のようなテストを行います。

  • AppSyncへのリクエストテスト
  • データソースに対する直接的なテスト

AppSyncへのリクエストテスト

jestなどのテストフレームを使用しAppSyncへ直接テストを行います。ローカルでAppSyncのサーバーを起動してテストしない理由は後述いたします。

  1. NodeJSでAppSyncClinetを作成
  2. AppSyncClinetからgraphqlを実行
  3. AWS SDKを利用して実際が更新されているか評価

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はしない理由とは?

ローカルで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' };
    });

関連記事

NodeJS クライアントアプリを作成する

まとめ

CIなどでテストを自動化する場合、AWS SDKをテストコード内でスタブ化するなどの考慮が必要になることがありますので、適宜テストフレームワークを活用して対応しましょう。私たちはNodeJSでテストを書くことが多いためJestで実現することが多数です。

graphqlでの開発・テストによる堅牢なシステム開発はお気軽に、お問い合わせください。