javascript の module import のテストを自動化する

# 前提

以下がインストール済みであること

* Node.js - v16.14.2
* mocha - v8.4.0
* chai - v4.3.6
* @babel/register - v7.17.7

# 0. テスト対象の script 構成

以下の様な javascript 階層があると仮定。

# 0.1. `src`ディレクトリ構成

* :open_file_folder: src
  * :open_file_folder:scripts
    *  :open_file_folder:bin
       * :pencil: apple.js
    *  :open_file_folder:lib
       * :pencil: orange.js
    *  :pencil:index.js

# 0.2. `src`ファイルの内容

```javascript:scripts/bin/apple.js
module.exports = () => {
  console.log(`apple.js`);
}
```

```javascript:scripts/lib/orange.js
module.exports = () => {
  console.log(`orange.js`);
}
```

```javascript:scripts/index.js
const apple = require("./bin/apple.js");
const orange = require("./lib/orange.js");

module.exports = {
  apple,
  orange,
}
```

:::note warn
以下の様な module では無い、runner 系の script では、本内容ではテスト不可。

```javascript:runner.js
const { apple, orange } = require("@/scripts");

apple();
//-> output "apple.js"

orange();
//-> output "orange.js"
```

上記の様な script を import すると、import -> 即実行される為。

```javascript:dont-example.js
require("./runner.js");
//-> output "apple.js"
//-> output "orange.js"
```

:::

# 1. `module-resolver`に`./src`の alias として`@`を割り当てる

# 1.1. `babel-plugin-module-resolver` をインストール

[公式](https://github.com/tleunen/babel-plugin-module-resolver)を参照。

```bash:gitbash
$ npm install --save-dev babel-plugin-module-resolver
```

# 1.2. babel.config.js を編集

プロジェクトルートに`babel.config.js`がある。無いなら作成する。

```diff_javascript:babel.config.js
module.exports = {
+  plugins: [
+    [
+      "module-resolver",
+      {
+        root: ["./src"],
+        alias: {
+          "@": "./src",
+        },
+      },
+    ],
+  ],
};

```

これで`babel-register`が`"@"`を`"./src"`として認識してくれる。

# 2. `.mocharc.js` を用意

以下の様にする

* :open_file_folder: mocha
  * :open_file_folder: config
    * :pencil: .mocharc.js

## 2.1. `.mocharc.js` を作成
```bash:gitbash
# プロジェクトルートにディレクトリを切る
$ mkdir -p mocha/config

# scriptファイルを作成
$ tech mocha/config/.mocharc.js
```

## 2.2 `.mocharc.js` を編集

テスト script (*.spec.js)を配置するディレクトリを事前に決めておく。
プロパティ spec に配列で割当する。
例では`tests/scripts`とする。

```javascript:.mocharc.js
"use strict";

/**
 * @see https://github.com/mochajs/mocha/blob/master/example/config/.mocharc.js
 */

module.exports = {
  color: true,
  diff: true,
  extension: ["js", "cjs", "mjs"],
  package: "./package.json",
  // @see https://mochajs.org/#reporters
  reporter: "spec",
  // @see https://babeljs.io/docs/en/babel-register/
  require: "@babel/register",
  spec: ["tests/scripts/**/*.spec.js"],
  ui: "bdd",
};
```

`reporter: "spec"`、`require: "@babel-register"`を忘れないこと。

# 3. テスト script を用意

以下の様にする

* :open_file_folder: tests
  * :open_file_folder: scripts
    * :pencil: sample.spec.js  

## 3.1. テスト script を作成する

先に決めたディレクトリに`sample.spec.js`として Javascript ファイルを作成する

```bash:gitbash
# ディレクトリを作成
$ mkdir -p tests/scripts

# js ファイルを作成
$ touch tests/scripts/sample.spec.js
```

# 3.2. テスト script ファイルを編集

以下の様にする。

```javascript:tests/scripts/sample.spec.js
"use strict";

const { describe, it } = require("mocha"); // mochaをインポート
const { expect } = require("chai"); // chai をインポート

const path = require("path");
const { INIT_CWD } = process.env; // プロジェクトルートの絶対パス
const CURRENT_FILE_PATH = path.relative(INIT_CWD, __filename); // プロジェクトルートからの相対パス

// test script
describe(`import test "${CURRENT_FILE_PATH}"`, () => {
  describe("/scripts", () => {
    describe("/bin", () => {
      describe("import apple.js", () => {
        it("const apple is Function", () => {
          const apple = require(`@/scripts/bin/apple.js`);
          expect(apple).to.be.an.instanceof(Function);
        });
      });
    });

    describe("/lib", () => {
      describe("import orange.js", () => {
        it("const orange is Function", () => {
          const orange = require(`@/scripts/lib/orange.js`);
          expect(orange).to.be.an.instanceof(Function);
        });
      })
    });

    describe("import index.js", () => {
      const { apple, orange } = require("@/scripts/index.js");

      it("const { apple } is Function", () => {
        expect(apple).to.be.an.instanceof(Function);
      });

      it("const { orange } is Function", () => {
        expect(orange).to.be.an.instanceof(Function);
      });
    });
  });
});

```

# 4. テストの実行

以下の様な構成になってるはず。

* :open_file_folder: mocha
  * :open_file_folder: config
    * :pencil: .mocharc.js
* :open_file_folder: src
  * :open_file_folder:scripts
    *  :open_file_folder:bin
       * :pencil: apple.js
    *  :open_file_folder:lib
       * :pencil: orange.js
    *  :pencil:index.js
* :open_file_folder: tests
  * :open_file_folder: scripts
    * :pencil: sample.spec.js


# 4.1 `mocha`を実行する

```bash:gitbash
# global インストールしている場合
# cd プロジェクトルート
$ mocha --config "mocha/config/.mocharc.js"
```

```bash:gitbash
# local インストールしている場合
$ node "./node_modules/mocha/bin/mocha" --config "mocha/config.mocharc.js"
```

# 4.2 結果の確認

```bash:gitbash
import test "tests\scripts\sample.spec.js"
    /scripts
      /bin
        import apple.js
          √ const apple is Function?
      /lib
        import orange.js
          √ const orange is Function?
      import index.js
          √ const { apple } is Function?
          √ const { orange } is Function?


  3 passing (3ms)
```

# メモ・注意点

* npm run script 等を作った後に、モジュールテストをする際に有用
* runner スクリプト（読み込み→即実行する系統のスクリプト）をテストするのには向かない。mochaテスト実行中に処理走っちゃうので。


以上