🤖

VitestでMariaDBのqueryメソッドをモックする

2022/06/03に公開

Vitest[1]でMariaDB[2]のqueryメソッドをモックする際に、少し躓いたので、その躓き点と解決策を共有しようと思います。
テスト初心者なので、何か間違いがあれば、ご指摘いただけると幸いです。

モックする方針

Jestのモック方法[3]のように、__mocks__にモックオブジェクトを入れてする方法があるかと思います。今回は、queryで発生したエラーが、呼び出すメソッドでキャッチできるかを試したかっただけなので、spyOnを使って簡単に実現したいと思いました(結果的に、こちらのほうが、とても時間がかかってしまったのですが…)。

テスト対象のメソッド

以下のコード内のcheckCardNameメソッドでqueryがエラーを吐いたときに、checkCardNameメソッドを呼び出したところでエラーを受け取れるかテストしたいと思います。

operate_mariadb.ts
import mariadb, { Connection, Pool } from "mariadb";
export default class OperateMariadb {
  pool: Pool;
  connection: Connection | undefined;
  constructor() {
    this.pool = mariadb.createPool({
      host: process.env.MARIADB_HOST,
      user: process.env.MARIADB_USER,
      password: process.env.MARIADB_PASSWORD,
      database: process.env.MARIADB_DATABASE,
    });
  }

  async getConnection(): Promise<void> {
    try {
      this.connection = await this.pool.getConnection();
    } catch (err: any) {
      throw new Error(err);
    }
  }

  async disconnection(): Promise<boolean> {
    if (this.connection) {
      try {
        await this.connection.end()
        return true;
      } catch (err: any) {
        throw new Error(err);
      }
    } else {
      throw new Error("The connection has not been established.")
    }
  }

  poolEnd() {
    this.pool.end();
  }
 
  // このメソッドをテストしたい
  async checkCardName(cardName: string): Promise<boolean> {
    try {
      if (this.connection) {
        const result = await this.connection.query(
          "select * from cards where card_name = ?",
          [cardName]
        );
        delete result.meta;
        const lengthObj = Object.keys(result).length;
        if (lengthObj > 0) {
          return true;
        } else if (lengthObj === 0) {
          return false;
        } else {
          throw new Error("Object length is invalid in checkCardName.");
        }
      } else {
        throw new Error("The database connection does not exist.");
      }
    } catch (err: any) {
      throw new Error(err);
    }
  }
}

とりあえず、書いてみた(動くけど、良くないコード)

とりあえず、自分が思ったように書いてみました。

chackCardName.test.ts
  it("con.query throws error", async () => {
    const operateMariadb = new OperateMariadb();
    await operateMariadb.getConnection();
    vi.spyOn(operateMariadb.connection, "query").mockImplementation(() =>
      Promise.reject(new Error("Query Error."))
    );

    expect(operateMariadb.checkCardName("king")).rejects.toThrow(
      "Query Error."
    );
    operateMariadb.disconnection();
    operateMariadb.poolEnd();
  });

checkCardName内でqueryを呼び出すときはawait this.connection.query("select * from cards where card_name = ?",[cardName]);ですが、これをspyOnでモックする方法で少し悩みました。
このコードは良くなさそうです。なぜなら、これをエディタで表示すると以下の画像のようにエラーが出ます。
しかし、このエラーを無視してテストしても、テストは通ります。

Error
エラーが出る箇所

The detail of the error
エラーの詳細

何が悪いのか

Vitestのエラーは簡単で、よく分からなかったので、Jestに突っ込んでみました。すると、以下のようなエラーが出ます。
The error Jest throws

なんだか、operateMariadb.connectionundefinedになるのが悪いって言ってそうです。
このエラーの前にif (operateMariadb.connection)を入れると、エラーが無く動きました。
最終に出来たコードは以下になります。


describe("Error handling testing.", () => {
  it("con.query throws error", async () => {
    const operateMariadb = new OperateMariadb();
    await operateMariadb.getConnection();
    if (operateMariadb.connection) { //これがないと、queryでエラーが出る
      vi.spyOn(operateMariadb.connection, "query").mockImplementation(() =>
        Promise.reject(new Error("Query Error."))
      );

      expect(operateMariadb.checkCardName("king")).rejects.toThrow(
        "Query Error."
      );

      operateMariadb.poolEnd();
    }
  });

  it("this.connection is undefined.", () => {
    const operateMariadb = new OperateMariadb();

    expect(operateMariadb.checkCardName("king")).rejects.toThrow(
      "The database connection does not exist."
    );

    operateMariadb.poolEnd();
  });
});

このコードの実行結果は、以下のGithub Actionsでも見ることが出来ます。
https://github.com/KASHIHARAAkira/vitest-playground/actions/runs/2432378503

脚注
  1. Vitest / Vitest https://vitest.dev/ ↩︎

  2. Node.js 用 MariaDB Connector / MariaDB https://mariadb.com/ja/resources/blog/node-js-connector/ ↩︎

  3. ES6 クラスのモック / Jest https://jestjs.io/ja/docs/es6-class-mocks ↩︎

Discussion