外部サービスへの接続を行うモジュールをJestでモックする際の注意点

2021.06.30

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

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

今回は、外部サービスへの接続を行うモジュールをJestでモックする際の注意点についてです。

環境

% node --version
v12.14.0
% jest --version
26.6.3

外部サービスへの接続を行うモジュールがモックできない

下記のようなmodule_bを呼び出すmodule_aがあります。

module_a.ts

import * as ModuleB from "./module_b";

/**
 * ユーザーを作成する
 * @param email ユーザーのEmailアドレス
 */
export const createUser = async (email: string): Promise<void> => {
  const password = "random-password";
  await ModuleB.createAuth0User(email, password);
};

module_bではSDKを使用して外部サービスへのデータ作成(Auth0へのユーザー作成)を行っています。

module_b.ts

import { ManagementClient } from "auth0";

const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN!;
const AUTH0_APP_CLIENT_ID = process.env.AUTH0_APP_CLIENT_ID!;
const AUTH0_APP_CLIENT_SECRET = process.env.AUTH0_APP_CLIENT_SECRET!;
const AUTH0_DATABASE_CONNECTION_NAME =
  process.env.AUTH0_DATABASE_CONNECTION_NAME!;

const auth0 = new ManagementClient({
  domain: AUTH0_DOMAIN,
  clientId: AUTH0_APP_CLIENT_ID,
  clientSecret: AUTH0_APP_CLIENT_SECRET,
  scope: "create:users",
});

/**
 * Auth0にユーザーを作成する
 * @param email ユーザーのEmailアドレス
 * @param password ユーザーのパスワード
 */
export const createAuth0User = async (
  email: string,
  password: string
): Promise<void> => {
  await auth0.createUser({
    connection: AUTH0_DATABASE_CONNECTION_NAME,
    email: email,
    password: password,
  });
};

この時module_aに対する次のようなJestのテストコードを作成しました。

test/module_a.test.ts

import { createUser } from "../module_a";
import { createAuth0User } from "../module_b";

test("createUser", async (): Promise<void> => {
  (createAuth0User as jest.Mock) = jest.fn().mockReturnValue({
    promise: jest.fn().mockResolvedValue(undefined),
  });

  const response = createUser("user01@example.com");

  expect(createAuth0User).toBeCalledTimes(1);
  expect(createAuth0User).toBeCalledWith(
    "user01@example.com",
    "random-password"
  );

  expect(response).toBeUndefined;
});

しかしこのテストコードを実行すると下記のようにエラーとなってしまいます。どうやらmodule_bのモックがうまく出来ていないようです。

% jest ./module_a.test.ts

 FAIL  test/module_a.test.ts
  ● Test suite failed to run

    ArgumentError: Must provide a domain

       7 |   process.env.AUTH0_DATABASE_CONNECTION_NAME!;
       8 |
    >  9 | const auth0 = new ManagementClient({
         |               ^
      10 |   domain: AUTH0_DOMAIN,
      11 |   clientId: AUTH0_APP_CLIENT_ID,
      12 |   clientSecret: AUTH0_APP_CLIENT_SECRET,

      at new ManagementClient (node_modules/auth0/src/management/index.js:106:11)
      at Object.<anonymous> (module_b.ts:9:15)
      at Object.<anonymous> (module_a.ts:1:1)
      at Object.<anonymous> (test/module_a.test.ts:1:1)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        3.443 s
Ran all test suites matching /.\/module_a.test.ts/i.

原因、解決

Jestではモジュールのモックを行う際にモジュールの内容が実際に実行されます。よって原因はモック時にnew ManagementClient()が動作し、パラメータが適切でないためクライアント初期化に失敗していたためでした。

そこで下記のようにnew ManagementClient()を関数化し、モック時に動作しないようにします。

module_b.ts

import { ManagementClient } from "auth0";

const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN!;
const AUTH0_APP_CLIENT_ID = process.env.AUTH0_APP_CLIENT_ID!;
const AUTH0_APP_CLIENT_SECRET = process.env.AUTH0_APP_CLIENT_SECRET!;
const AUTH0_DATABASE_CONNECTION_NAME =
  process.env.AUTH0_DATABASE_CONNECTION_NAME!;

const createManagementClient = () => {
  return new ManagementClient({
    domain: AUTH0_DOMAIN,
    clientId: AUTH0_APP_CLIENT_ID,
    clientSecret: AUTH0_APP_CLIENT_SECRET,
    scope: "create:users",
  });
};

/**
 * Auth0にユーザーを作成する
 * @param email ユーザーのEmailアドレス
 * @param password ユーザーのパスワード
 */
export const createAuth0User = async (
  email: string,
  password: string
): Promise<void> => {
  const auth0 = createManagementClient();
  await auth0.createUser({
    connection: AUTH0_DATABASE_CONNECTION_NAME,
    email: email,
    password: password,
  });
};

するとテストが通るようになりました。

% jest ./module_a.test.ts

 PASS  test/module_a.test.ts
  ✓ createUser (4 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.703 s, estimated 3 s
Ran all test suites matching /.\/module_a.test.ts/i.

おわりに

外部サービスへの接続を行うモジュールをJestでモックする際の注意点についてでした。

今回はAuth0のSDKを例に紹介をしましたが、他のSDKやパッケージなどの場合でも同様の方法が有用となりそうです。

以上