TypeScript + mocha + power-assert でプロジェクトルートに tsconfig.json を置いていない時のテスト方法

TypeScript で mocha + power-assert + espower-typescript でテストを行う際に、プロジェクトルートディレクトリに tsconfig.json を置かないでテストをする方法が [espower-typescript 公式のドキュメント](https://github.com/power-assert-js/espower-typescript)に記載されていなかったので、まとめました。

## ディレクトリ構成例
今回、テストを行うディレクトリ構成と説明を以下に示します。

```
PROJECT_ROOT
├── dist
│     └── hoge.js
└── test
      └── foo.ts
      └── tsconfig.json
```
* dist
 * テストされる JavaScript が有るディレクトリ
* test
 * テストする TypeScript が有るディレクトリ。tsconfig.json はここにある

## espower-typescript の guess.js で行っていること
本題に入る前に、 espower-typescript の guess.js で行っている処理を確認します。
mocha で espower-typescript を使用してテストする際の最も有名なコマンドは下記のものだと思います。

```bash
$ mocha --compilers ts:espower-typescript/guess test/**/*.ts
```
このコマンドで呼び出される、[guess.js](https://github.com/power-assert-js/espower-typescript/blob/091ce3d950f84620325becc74bd15aa2122603eb/guess.js) の処理はおおむね以下のように処理されています。

1. カレントディレクトリにある package.json の解析
 * テストディレクトリなどの設定取得
2. カレントディレクトリにある tsconfig.json の解析
 * TypeScript コンパイラオプションなどの設定取得
3. index.js にカレントディレクトリのパス、TypeScript コンパイラオプションの情報を渡し実行

テストを実行する際はほぼプロジェクトルートディレクトリで行われると思うので、カレントディレクトリはプロジェクトルートディレクトリに読み替えることが出来ます。
この時、手順 2においてプロジェクトルートディレクトリに tsconfig.json が存在しないため `null` が返ってきてしまいコンパイラオプションを取得できず、テスト実行に失敗してしまいます。

## ローダの設定とテストの呼び出し方
espower-typescript では、[espower-babel と同じやり方](https://github.com/power-assert-js/espower-babel#more-customization)で guess.js を使わずにテストを行うことが出来ます。以下はローダの動作設定が書かれた JavaScript ファイルです。注意点としては、`parseTsConfig(config)`内で Typescript モジュールを呼び出すため、espower-typescript 内で使用されている Typescript モジュールのパスを設定する必要があります。

```js:espower-typescript-loader.js
'use strict';

/*
  espower-typescript のローダ
  https://github.com/power-assert-js/espower-babel#more-customization を基に作成
 */

const path = require('path');
const fs = require('fs');
const espowerTsPath = require.resolve('espower-typescript');
const ts = require(`${path.relative(__dirname, path.dirname(espowerTsPath))}/node_modules/typescript`)

const tsconfigDir = 'test';
let tsconfigPath = `${tsconfigDir}/tsconfig.json`;
let tsconfigBasepath = null;
let compilerOptions = null;
if (tsconfigPath) {
  compilerOptions = parseTsConfig(tsconfigPath);
  tsconfigBasepath = path.dirname(tsconfigPath);
}

require('espower-typescript')({
    // directory where match starts with
    cwd: tsconfigPath,

    // glob pattern using minimatch module
    pattern: 'test/**/*.ts',

    // options for espower module
    espowerOptions: {
        patterns: [
            'assert(value, [message])',
            'assert.ok(value, [message])',
            'assert.equal(actual, expected, [message])',
            'assert.notEqual(actual, expected, [message])',
            'assert.strictEqual(actual, expected, [message])',
            'assert.notStrictEqual(actual, expected, [message])',
            'assert.deepEqual(actual, expected, [message])',
            'assert.notDeepEqual(actual, expected, [message])'
        ]
    },
    compilerOptions : compilerOptions
});

function parseTsConfig(tsconfigPath) {
  var parsed = ts.parseConfigFileTextToJson(tsconfigPath, fs.readFileSync(tsconfigPath, 'utf8'));
  if (parsed.error) {
    throw new Error(parsed.error.messageText);
  }

  if (!parsed.config || !parsed.config.compilerOptions) {
    return null;
  }

  return parsed.config.compilerOptions;
}
```

テストの呼び出し方は以下のようなコマンドになります。

```bash
$ mocha --require ./src/test/espower-typescript-loader src/test/**.ts
```

## テンプレート例
* [Github](https://github.com/tom-konda/espower-typescript-template-example)
* [Bitbucket](https://bitbucket.org/tom-konda/espower-typescript-template-example/src)

## まとめ
以上をまとめると、下記のようになります。

* tsconfig.json をプロジェクトルートディレクトリ以外に設置した場合は、espower-typescript を使用して呼び出すときにローダを作成する必要がある。
* tsconfig.json をプロジェクトルートディレクトリに置かず、用途ごと（開発用、テスト用）に分けることが可能になる。