JestでUUIDの生成処理をテストする

2021.11.24

こんにちは、CX事業本部 IoT事業部の若槻です。

Jestは、JavaScriptのテスティングフレームワークです。Facebookが開発しています。

今回は、JestでUUIDの生成処理をテストする方法を確認してみました。

できた方法

テスト対象コード

テスト対象のコードは、下記のようなAWS Step Functionsステートマシンを実行するLambda関数ハンドラーです。

src/lambda/handlers/sampleHandler.ts

import * as AWS from 'aws-sdk';
import { v4 as uuidv4 } from 'uuid';

const stateMachineArn = process.env.STATE_MACHINE_ARN as string;

export const sfn = new AWS.StepFunctions();

export const handler = async (prefix: string): Promise<void> => {
  const uuid = uuidv4();

  await sfn
    .startExecution({
      stateMachineArn: stateMachineArn,
      name: `${prefix}_${uuid}`,
    })
    .promise();
};

startExecution()でステートマシンを実行する際に、name(実行名)を「プレフィクス + UUID」で指定しています。

UUIDの生成に使用しているのは下記のライブラリです。これによりランダムなRFC4122のUUIDを生成できます。

テストコード(uuidをモックする方法)

toBeCalledWith()を使用して、startExecution()が期待した引数により実行されているかをテストします。uuidをモックすることにより固定のUUIDを生成するようにしています。

import { v4 as uuidv4 } from 'uuid';

const stateMachineArn = 'dummyStateMachineArn';
process.env.STATE_MACHINE_ARN = stateMachineArn;

import { handler, sfn } from '../src/lambda/handlers/sampleHandler';

jest.mock('uuid');

test('handler', async (): Promise<void> => {
  (sfn.startExecution as jest.Mock) = jest.fn().mockReturnValue({
    promise: jest.fn().mockResolvedValue(undefined),
  });
  (uuidv4 as jest.Mock).mockReturnValue('uuid-123');

  await handler('01');

  expect(sfn.startExecution).toBeCalledWith({
    stateMachineArn: stateMachineArn,
    name: '01_uuid-123',
  });
});

テストコード(expect.any()を使用する方法)

こちらのパターンでは、同じくtoBeCalledWith()を使用していますが、uuidのモックはしていません。

test/sampleHandler.test.ts

const stateMachineArn = 'dummyStateMachineArn';
process.env.STATE_MACHINE_ARN = stateMachineArn;

import { handler, sfn } from '../src/lambda/handlers/sampleHandler';

test('handler', async (): Promise<void> => {
  (sfn.startExecution as jest.Mock) = jest.fn().mockReturnValue({
    promise: jest.fn().mockResolvedValue(undefined),
  });

  await handler('01');

  expect(sfn.startExecution).toBeCalledWith({
    stateMachineArn: stateMachineArn,
    name: expect.any(String),
  });
});

生成されたUUIDの文字列の部分は、expect.any(constructor)を使用して任意のString型であることをテストしています。

expect.any(constructor) matches anything that was created with the given constructor. You can use it inside toEqual or toBeCalledWith instead of a literal value.

できたけど宜しくない方法

最初UUIDの生成をするコードのテストを書こうとした時、UUIDをテスト対象コード側でExportして、テストコード側で使うことによりテストを行おうとしました。

テスト対象コード

src/lambda/handlers/sampleHandler.ts

import * as AWS from 'aws-sdk';
import { v4 as uuidv4 } from 'uuid';

const stateMachineArn = process.env.STATE_MACHINE_ARN as string;

export const sfn = new AWS.StepFunctions();
export const uuid = uuidv4();

export const handler = async (prefix: string): Promise<void> => {
  await sfn
    .startExecution({
      stateMachineArn: stateMachineArn,
      name: `${prefix}_${uuid}`,
    })
    .promise();
};

テストコード

UUIDをImportしてtoBeCalledWith()の中で使用しています。これによりuuidのモックおよびexpect.any(constructor)の使用を不要としました。

test/sampleHandler.test.ts

const stateMachineArn = 'dummyStateMachineArn';
process.env.STATE_MACHINE_ARN = stateMachineArn;

import { handler, sfn, uuid } from '../src/lambda/handlers/sampleHandler';

test('handler', async (): Promise<void> => {
  (sfn.startExecution as jest.Mock) = jest.fn().mockReturnValue({
    promise: jest.fn().mockResolvedValue(undefined),
  });

  await handler('01');

  expect(sfn.startExecution).toBeCalledWith({
    stateMachineArn: stateMachineArn,
    name: `01_${uuid}`,
  });
});

上記のテストコードもパスはするのですが、Lambda関数でハンドラー外で値をUUIDなどを生成すると、下記エントリのようなバグを埋め込むことになるので、宜しくない方法となります。

まとめ

「uuidをモックする方法」であれば生成したUUIDを加工して使用するコードの場合にも、期待した加工ができているかテストができるので、基本的にはこちらの方法を使用すればよいかと思います。

参考

以上