AWS SDK Jest mock VS spyOn ! Lambda SDP 認定企業が語るAWS SDKのテスト手法比較&まとめ😎

AWS SDK Jest mock VS spyOn ! Lambda SDP 認定企業が語るAWS SDKのテスト手法比較&まとめ😎

こんにちは!

最近は、AWS Lambda の TypeScript 開発が益々一般的になってきました、フロントエンドの言語とサーバーサイドの言語を統一するユニバーサル JS アーキテクチャーが世界的に流行っている傾向にあります。私達は、フルスタックエンジニアがバックエンド&フロントエンドの開発それぞれを行うというプロジェクトが多い為、早期にユニバーサル JS に取り組んできました。

本記事では、AWS SDK を使用した AWS Lambda のソースコードに対するユニットテスト手法についてエキスパートが解説します。

想定する読者

  • AWS Lambda を TypeScript で実装しているヒト
  • Jest のテストで jest mockjest spyOn どちらを採用すべきか悩んでいるヒト
  • AWS SDK を含むソースコードのテスト方法を知りたいヒト

はじめに

本記事を読む前に理解しておきたいコト

以下の技術要素は先に理解/経験しておくと本記事の情報がスムーズにキャッチアップ可能です。

  • TypeScript のソースコードに対するユニットテストの実装経験
  • Jest のユニットテストコードを TypeScript で実装した経験
  • AWS SDK v3 を使用して AWS のリソースと通信した経験

本記事で対象とするAWS SDKはバージョン3

AWS SDK バージョン2に対する記事ではございませんので、参考にして実装される場合はご注意ください。また、もしプロジェクトで AWS SDK バージョン2を使用している場合は、早めにバージョン3へアップグレードされることを推奨します。バージョン3は TypeScript との親和性が高くバージョン2時代よりも、更にタイプセーフな実装が可能となります。

jest mock VS jest spyOn !

まずはそれぞれのモック化実装方法を解説します。

jest mock によるモック実装方法と解説

特定のモジュールをモック化する事ができます。以下はAWS開発でよく使用される jest mock の実装例です。

// DynamoDB Client のモック化の例
jest.mock('@aws-sdk/client-dynamodb', () => {
  return {
    DynamoDBClient: jest.fn().mockImplementation(() => {
      return {};
    }),
  };
});
enum LibDynamodbCommand { 
  GetCommand = 'GetCommand'
}
jest.mock('@aws-sdk/lib-dynamodb', () => {
  return {
    DynamoDBDocumentClient: {
      from: jest.fn().mockImplementation(() => {
        return {
          send: jest.fn().mockImplementation((command: Record<string, unknown>) => {
            if (command.name == LibDynamodbCommand.GetCommand) {
              return Promise.resolve('return value for GetCommand here');
            }
            return Promise.resolve('return value here');
          }),
        };
      }),
    },
    GetCommand: jest.fn().mockImplementation(() => {
      return { name: LibDynamodbCommand.GetCommand };
    }),
  };
});

// axiosのモック化の例
const mockPost = jest.fn();
const mockGet = jest.fn();
const mockPut = jest.fn();
const mockDelete = jest.fn();
jest.mock('axios', () => ({
  create: () => ({
    post: mockPost,
    get: mockGet,  
    put: mockPut,
    delete: mockDelete,
  }),
}));

上記のソースコードのように、モジュール自体をモック化する手間がかかるものの、モック定義を 1 箇所に集約できるため、以降のテストコード中でその都度モック化をせずに済み、テスト全体の可読性が向上する可能性があります。spyOn と比較するとモノリシックなモックとも言えますね。

jest spyOn によるモック実装方法と解説

モジュールの特定の関数等をモック化することが出来ます。以下はAWS開発でよく使用される jest spyOn の実装例です。

import * as LibDynamodb from '@aws-sdk/lib-dynamodb';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({
  region: "yoru region",
});
const docClient = LibDynamodb.DynamoDBDocumentClient.from(client);
const mockSend = jest.spyOn(docClient, 'send').mockResolvedValue({});

ご覧のとおり、モジュールの中の特定の関数のみをモック化することが出来ます。テストコード中で、各テストケース毎に必要なモジュールの一部だけをモックを定義できる為、テストケースごとに疎結合なモック実装が可能です。

どちらが優れている?

ユースケース次第ですが、AWS SDK のモック化の場合、わたしたちは spyOn を使用する事が多いです。以下の比較表をご覧ください。

モック化の方法メリット
jest mockモック化の処理を1箇所に集約できる
テストコードをシンプルに書ける
jest spyOnテストケース毎にモック定義できる
テストケース毎にモックを疎結合に書ける

わたしたちは日々、AWS Lambda のユニットテストを実装するケースが非常に多く、AWS Lambda が持つ AWS SDK に関する処理は通常ユースケースが幅広く様々です。そのため、テストケースを新しく追加したいと思った時に、テストケース毎にモックが疎結合な方がテストが書きやすいと考えます。

ただ、 jest mock の場合においてもグローバルに jest.fn を用意し、テストケース毎に振る舞いを変更することももちろん可能です。

言いたいのは、AWS SDK のモック化を考えた時、 jest spyOnjest mock どちらも同じ事が実現可能で、書き方として私達が好むのは jest spyOn ということです。

モックがグローバルに同じ振る舞いをするなら、当然 jest mock に軍配があがりますが、モックの振る舞いをテストケース毎に柔軟にしたいことが前提なら、 jest spyOn の方は書きやすいかなと感じます。

まとめ

本記事では、AWS SDK を含むユニットテストの jest mockjest spyOn について解説しました。ユニットテストの書き方はプロジェクトによって様々かとは思いますが、ある程度使用するモジュールの指針などは決めておいたほうが良いかなと感じます。

サーバーレスの開発はお気軽にお問い合わせください。