こんにちは、るおんです。
皆さんはTypeScriptで書いたコードのテストをどう書いていますか?今回は、Node.jsのテストフレームワークMochaとアサーションライブラリであるChaiを使用してTypeScriptのテストを書いて行きたいと思います。
また、nycを用いてテストカバレッジも取得できるようにしてみたいと思います。
このブログでやること
- 環境構築
- TypeScriptコードをそのままテスト
- MochaとChaiを使ったテストコードの記述
- nycでのテストカバレッジ取得
環境構築
今回使用した動作環境は以下のとおりです。
- OS : MacOS Ventura 13.6.1
- Node.js : v20.0.0
- npm : v9.6.4
- tsc:v5.3.3
プロジェクト作成
まず、新しいプロジェクトディレクトリを作成します。
mkdir ts-test-demo
cd ts-test-demo
必要なライブラリをインストール
次に、必要なライブラリをインストールします。
npm install mocha chai@^4.0.0 ts-node cross-env @types/mocha @types/chai --save-dev
各ライブラリの役割は以下の通りです。
- mocha: テストフレームワーク
- chai: アサーションライブラリ
- @types/mocha: MochaのTypeScript型定義
- @types/chai: ChaiのTypeScript型定義
- ts-node: TypeScriptをJavaScriptにトランスパイルせずに直接実行するためのツール
- cross-env: 環境変数を設定するためのユーティリティ
- nyc: テストカバレッジを測定するためのツール
Mocha v10でChai v5を実行しようとするとエラーが発生したため、Chaiをv4で固定しています
Chai v5 - Unknown file extension ".ts" when running with mocha v10
MochaとChaiはどちらもJavaScriptのテスティングに使用されるツールですが、役割が異なります。Mocha はテストフレームワークであり、テストの構造化、実行、結果のレポート生成などを担当する一方、Chai はアサーションライブラリであり、テストケースの期待値と実際の結果を比較するための豊富なアサーションメソッドなどを提供してくれます。両者を組み合わせることでテストの実行とアサーションの機能を完備できます。一応、Mocha は様々なアサーションライブラリと連携できるためChai 以外のライブラリも選択できます。
Mochaは本来JavaScriptで動作するのですが、ts-nodeを使用することでTypeScript コードを直接実行できるようにすることができます。通常、TypeScriptコードを実行するにはコンパイラを使用してJavaScript に変換する必要があります。ts-nodeはこのプロセスを簡素化し、TypeScriptコードを直接実行できるようにしてくれるため、今回使用していきます。
testコマンド修正
package.jsonのscriptsセクションに、テスト実行コマンドを追加します。
"scripts": {
    "test": "cross-env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' nyc mocha -r ts-node/register 'tests/*.ts'"
  }
このコマンドでは、以下のことを行っています。
- cross-envを使って、TypeScriptのモジュールシステムをCommonJSに設定する環境変数を設定。
- nycを使ってテストカバレッジを測定。
- ts-nodeを使ってTypeScriptのテストファイルを実行できるように登録。
- mochaを使って、- tests/ディレクトリ以下のすべてのTypeScriptファイル(- **.ts)をテストとして実行。
こうすることで、npm testコマンドを使用するだけでtestsディレクトリ内のテストを全て実行できるようにしています。
テスト作成
関数作成
それでは今回のテスト対象となる関数を作成していきます。以下は、受け取った12桁の数字の文字列を、awsのアカウントidのフォーマットに整形する関数です。
例えば、"123412341234"という12桁の数字は"1234-1234-1234"というようにフォーマットされます。
 
export const formatAwsAccountId = (idStr: string): string => {
  const isNumberString = !isNaN(Number(idStr));
  if (isNumberString === false) {
    throw new Error("数字ではありません");
  }
  if (idStr.length !== 12) {
    throw new Error("12桁ではありません");
  }
  // 四桁区切りに「-」を挿入
  // 123412341234 => 1234-1234-1234
  const replacedId = idStr.replace(/(\d{4})(?=\d)/g, "$1-");
  return replacedId;
};
数字を受け取らなかった場合や、12桁でない場合はエラーを投げるようにしています。
テスト作成
次に、テストを記述していきます。まずtestsディレクトリを作成し、formatAwsAccountId.test.tsファイルを作成します。以下のようにテストを記述します。
import { describe, it } from "mocha";
import { assert } from "chai"
import { formatAwsAccountId } from "../formatAwsAccountId";
describe('formatAwsAccountId', () => {
  it("'123412341234'の時、'1234-1234-1234'", () => {
    assert.equal(formatAwsAccountId("123412341234"), "1234-1234-1234");
  });
  it("'あいうえお'の時、エラー'数字ではありません'", () => {
    assert.throws(() => formatAwsAccountId("あいうえお"), '数字ではありません');
  });
  it("'12345'の時、エラー'数字ではありません'", () => {
    assert.throws(() => formatAwsAccountId("12345"), '12桁ではありません');
  });
})
このテストでは、以下のことを確認しています。
- 引数が正しい形式の文字列の場合、期待通りに整形されること
- 引数が数字でない場合、エラーがスローされること
- 引数が12桁でない場合、エラーがスローされること
以下は、簡単なテストコードの説明です。
describeは、モジュールやクラス、関数などのまとまりを表現するために使用します。今回はformatAwsAccountIdという関数単位でまとめています。
itは、個々のテストケースを表現するために使用します。第1引数にテストケースの内容を文字列で指定し、第2引数にテストケースの処理を記述するコールバック関数を指定します。
assert.equalは、2つの値が等しいかどうかを検証するために使用します。第1引数に実際の値、第2引数に期待される値を指定します。
assert.throwsは、関数が例外をスローするかどうかを検証するために使用します。第1引数に例外をスローすると予想される関数、第2引数に期待されるエラーメッセージを指定します。
 
参考:
実行
npm testコマンドを実行すると、テストが実行されます。
=> % npm test                                                                                                                                                                                                                                                                                                13:58:59
> test
> cross-env TS_NODE_COMPILER_OPTIONS='{"module": "commonjs" }' nyc mocha -r ts-node/register 'tests/*.ts'
  formatAwsAccountId
    ✔ '123412341234'の時、'1234-1234-1234'
    ✔ 'あいうえお'の時、エラー'数字ではありません'
    ✔ '12345'の時、エラー'数字ではありません'
  3 passing (2ms)
-----------------------|---------|----------|---------|---------|-------------------
File                   | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------------------|---------|----------|---------|---------|-------------------
All files              |     100 |      100 |     100 |     100 |                   
 formatAwsAccountId.js |     100 |      100 |     100 |     100 |                   
-----------------------|---------|----------|---------|---------|-------------------
三つのテストが通ることがわかると思います。また、nycによってテストカバレッジが出力されていることも確認できます。
この結果から、src/formatAwsAccountId.tsファイルのテストカバレッジが100%であることがわかります。
以上でMochaとChaiとnycを使用してTypeScriptのコードをテストすることができました!
参考資料
typescriptでテストを書く環境を作ってみた(mocha, chai)
Mocha/Chai with TypeScript (2023 update)
Mocha - the fun, simple, flexible JavaScript test framework
Chai.js
nyc