⚗️

モジュールじゃない JavaScript コードを Vitest でテストする

2024/12/25に公開

お題

ぜんぜんモジュールじゃない、以下のような JavaScript ファイルについて、Vitest でテストを書くことになりました。

app/sum.js
function sum(a, b) {
  return a+ b;
}

Vitest テストプロジェクトを立ち上げる

まずは以下のようにして、test フォルダに Vitest のテストプロジェクトを新規作成します。

$ mkdir test
$ cd ./test
$ npm init -y
$ npm install --save-dev vitest
$ _

続けて、この test フォルダ内に生成された package.json を編集し、npm run test を実行すれば Vitest のテストランナーによってテストが実行されるようにします。

test/package.json
{
  "name": "test",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "test": "vitest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "vitest": "^2.1.8"
  }
}

テストコードを記述する

次に、実際のテストコードを書きます。test フォルダ内に sum.test.ts を作成し、以下のように記述します。エディタは、自分は主に VSCode を使っているので、下記コードも VSCode で編集しました。

test/sum.test.ts
import { describe, test, expect } from 'vitest';
import '../app/sum';

describe("test for sum.js", () => {
    test("sum(1, 2) should return 3", () => {
        expect(sum(1, 2)).toBe(3);
    });
});

前述のとおり、app/sum.js は古典的な JavaSrcipt コードで、グローバル名前空間に "sum" という名前の function をじかに宣言しています。このような ESModule 形式でない JavaScript ファイルを、Vitest のテストコードからどうやって参照するのかがよくわかりませんでした。

とりあえず恐る恐る import '../app/sum'; と記述してみたところ、とりあえず VSCode エディタ画面上はとくにエラーが表示されることはなく、また、下図のように sum.js 内で定義されている sum 関数がコード補完の候補に出てきますし、また、その関数シグネチャも正しく表示されましたので、この方法でいいのかな、と思いました。

テストを実行する... が "sum is not defined"

さて、テストコードの実装を終えましたので、テストを実行してみました。ターミナルから npm test を実行することで、あるいは VSCode に Vitest の拡張をインストールしてある場合は、VSCode の Test Explorer から、テストを実行できます。

すると、残念ながら、"sum is not defined"、関数 sum は定義されていない、というエラーになってしまいました。

VSCode エディタ画面上での判断と、Vitest によるテストコードの実行時とでは、import の処理の仕方が違うようです。

ChatGPT に聞いたり試行錯誤するもうまくいかず

詳細は割愛しますが、ChatGPT に聞きながら、vitest.config.tstsconfig.json を用意したり、それらをいろいろひねくりまわしてみましたが、どうにもうまく "sum is not defined" を解決できませんでした。

結局 eval 使いました

どうにもうまくいかなかったので、ChatGPT からの回答の最後のひとつにあった方法、app/sum.js の内容を文字列として変数に読み取り、 eval を使ってその文字列を評価する、という方法で、どうにかテストを実行することができました。具体的には、次のように進めました。

このテストコードは、(とくに環境を明示しませんでしたので) Node.js 上で動作していることから、Node.js の fs モジュールなどを使って JavaScript ファイルの内容を読み取ることにしますので、ターミナルから、以下のように Node.js モジュールの TypeScript 型定義を追加しておきます。

$ npm install --save-dev "@types/node"
$ _

その上で、テストコードを以下のように実装します。つまり、以下のことを行ないます。

  1. app/sum.js ファイルの内容を、Node.js の fs モジュールを使って文字列として読み込み、
  2. その文字列の末尾に sum; を付け加えて、この JavaScript 文字列を評価したときに、sum 関数が返るように加工し、
  3. 加工後の JavaScript 文字列を eval 関数に渡して評価実行、
  4. eval による評価結果、すなわち同関数の戻り値を変数 sum に格納
  5. sum を使用してテスト実施
test/sum.test.ts
import { readFileSync } from 'fs';
import { describe, test, expect } from 'vitest';

const scriptContent = readFileSync('../app/sum.js', 'utf-8') + `\n sum;`;
const sum = eval(scriptContent) as (a: number, b: number) => number;

describe("test for sum.js", () => {
    test("sum(1, 2) should return 3", () => {
        expect(sum(1, 2)).toBe(3);
    });
});

以上でどうにかテストが実行できるようになりました。

本当にこれでいいの?

とりあえず動いたわけですが、eval を使うなど、なんだか負けた感じがしてたまりません。もっとまともな方法があれば知りたいです。

Discussion