TypeScript でコードを書いた際、ソースのテストは通っていても、何かの弾みでビルドされたコードが壊れていないことを念のために確認しておきたいという需要はある。とくにライブラリを作っていて、npm に公開する場合など、publish 前に検証するのは良い考えだろう。
このために、同じテストコードをソースと生成物それぞれに対して適用する方法を考える。つまり、以下のような conditional import を行いたい。
// このコードは動かない。
if (process.env.TESTMODE === 'dist') {
import { FooClass, FooString } from '../dist/Foo';
}
else
import { FooClass, FooString } from '../src/Foo';
}
describe('Test foo', function() {
it('works', function() {
const fooString: FooString = 'foo'; // このように型やインターフェースも使いたい
assert(FooClass.getFooString() === fooString);
});
});
TypeScript の import ... from
文は、名前空間やモジュールの直下でなければ使えないため、上記のように単純にはいかないし、インポート元は文字列リテラルでなければいけないので、import { FooClass } from `../${getTestDir()}/Target`
とすることもできない。
動的な require
は可能なので、const { FooClass } = require(`../${getTestDir()}/Foo`);
とすることはできるが、型情報が失われるのでテストを書く際に型による支援を受けられづらくなる。
とはいえ、今回のケースに限れば、読み込み元が変わったとしても、型は変わらないので、以下のようにして型情報をソースから流用することで解決する。FooClass
の型情報を使うためだけにソースから FooClass
を _FooClass
としてインポートし、実体が require
される先の変数の型として、typeof _FooClass
としている。インターフェースはソースから直接読み込むしかない。
import { FooClass as _FooClass, FooString } from '../src/Foo';
let FooClass: typeof _FooClass;
if (process.env.TESTMODE === 'dist') {
FooClass = require('../dist/Foo').FooClass;
}
else {
FooClass = require('../src/Foo').FooClass;
}
describe('Test foo', function() {
it('works', function() {
const fooString: FooString = 'foo'; // このように型やインターフェースも使いたい
assert(FooClass.getFooString() === fooString);
});
});
あるいはテンプレート文字列を使うなら以下のようになるだろう。
import { FooClass as _FooClass, FooString } from '../src/Foo';
// getTestDir() は環境変数を見て適当な読み込み元ディレクトリを返す関数とする。
const FooClass: typeof _FooClass= require(`../${getTestDir()}/Foo`).FooClass;
以上のようにしておくと、package.json
に
"scripts": {
"test": "mocha --require espower-typescript/guess test/**/*.ts",
"disttest": "TESTMODE=dist npm run test",
"prepublishOnly": "npm test && npm run disttest"
}
としておくことで、npm test
でソースのテストを、npm run disttest
でビルド生成物のテストを実行できる。また publish
前には prepublishOnly
によって自動で両方のテストが走り、動かないパッケージを公開してしまうことを避けられる。
なお、TypeScript 2.4 で導入された Dynamic Import を使う方法もあるが、モジュールが非同期で読み込まれるため Promise の解決をしなければならない。テストのために雑に読み込みたい状況であれば、単純に require
したほうが楽だろう。