Vitest で UUIDv4 のテストを行う(モックを使う場合/使わない場合)

2024.02.01

こんにちは、CX 事業本部製造ビジネステクノロジー部の若槻です。

Node.js パッケージの uuid を使うと、RFC4122 に則った UUID を生成することができます。

例えば UUIDv4 の場合は、以下のように使用して生成を行います。これにより衝突させたくない ID 属性などに使用する文字列を簡単に取得することが出来て便利です。

import { v4 as uuidv4 } from 'uuid';
uuidv4(); // ⇨ '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'

今回は、uuid を使って UUIDv4 の文字列を生成するモジュールのテストを、テスティングフレームワークの Vitest で行う機会があったので、モックを使う場合および使わない場合をそれぞれご紹介します。

試してみた

Node.js & TypeScript のプロジェクト作成

# package.json を作成
npm init -y

# TypeScript をインストール
npm install typescript --save-dev

# TSConfig を作成
npx tsc --init --rootDir src --outDir lib --esModuleInterop --resolveJsonModule --lib es6,dom --module commonjs

# Node.js の型定義をインストール
npm install @types/node --save-dev

ソースコード

テスト対象の関数のソースコードを作成します。

uuid をインストールします。

npm i uuid
npm i --save-dev @types/uuid

src/create-company.ts を作成します。

mkdir src
touch src/create-company.ts

下記が関数のソースコードです。処理内容は、引数で受け取った namename に設定し、id には uuid を使用して生成した文字列を設定しています。

src/create-company.ts

import * as Uuid from 'uuid';

export interface CreatedCompany {
  id: string;
  name: string;
}

export const createCompany = async (params: {
  name: string;
}): Promise<CreatedCompany> => {
  return { id: Uuid.v4(), name: params.name };
};

テストコード

Vitest をインストールします。

npm i vitest

src/create-company.test.ts を作成します。

touch src/create-company.test.ts

下記がテストコードです。

src/create-company.test.ts

import { createCompany } from './create-company';
import { describe, test, vi, expect } from 'vitest';

describe('createCompany', (): void => {
  test('モックを使う場合', async (): Promise<void> => {
    vi.mock('uuid', () => {
      return {
        v4: vi.fn().mockReturnValue('e3162725-4b5b-4779-bf13-14d55d63a584'), // dummy-id
      };
    });

    const result = await createCompany({ name: 'dummy-name' });

    expect(result).toEqual({
      id: 'e3162725-4b5b-4779-bf13-14d55d63a584',
      name: 'dummy-name',
    });
  });

  test('モックを使わない場合', async (): Promise<void> => {
    const companyIdRegexPattern = 
      /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/; // UUIDv4 の正規表現パターン

    const result = await createCompany({ name: 'dummy-name' });

    expect(result).toMatchObject({
      id: expect.any(String),
      name: 'dummy-name',
    });

    expect(result.id).toMatch(companyIdRegexPattern); // マッチャーによる ID 値の検証
  });
});

  • モックを使う場合は vi.mock を使用し、モック対象のモジュールのパス(uuid、第一引数)と、モック化されたモジュールファクトリー(第二引数)を指定して、エクスポートオブジェクトを作成します。
  • モックを使わない場合は、UUIDv4 の正規表現パターンを作成し、マッチャーによる ID 値の検証で使用します。

Vitest でテストを実行すると、パスすることが確認できます。

$ npx vitest run --dir ./src

 RUN  v1.2.2 /Users/wakatsuki.ryuta/projects/other/vitest-sample

 ✓ src/create-company.test.ts (2)
   ✓ createCompany (2)
     ✓ モックを使う場合
     ✓ モックを使わない場合

 Test Files  1 passed (1)
      Tests  2 passed (2)
   Start at  02:57:50
   Duration  263ms (transform 72ms, setup 0ms, collect 67ms, tests 1ms, environment 0ms, prepare 47ms)

おわりに

Vitest で UUIDv4 のテストを、モックを使う場合と使わない場合とで行ってみたのでご紹介しました。

使い分けとしては、関数のモックが可能な単体テストでは前者のモックを使う方法、モックが難しい結合テストではモックを使わない後者を取るなどになると思います。

モックをする場合の注意点として、vi.mock は巻き上げ(hoist)が発生し、実行時に記述がファイルの先頭に移動するため、1 つのファイル内で vi.mock を複数回使用している場合は vi.hoisted を使用する必要があります。

vi.mock is hoisted (in other words, moved) to top of the file. It means that whenever you write it (be it inside beforeEach or test), it will actually be called before that.

vi.hoisted についてはまた別の機会にてご紹介します。

参考

以上