はじめに
JavaScriptのテストフレームワークを選ぶとき、今はJestやVitestのような「アサーションもモックもカバレッジも全部入り」のオールインワン型が主流です。導入すればすぐ書き始められる手軽さは確かに魅力ですが、その裏では「アサーションの書き方」も「モックの挙動」も、フレームワークが決めたルールに縛られることになります。
Mochaはそこが違います。テストを組み立てて実行するランナーとしての役割に徹し、アサーションには標準のassertでもChaiでもPowerAssertでも、好きなものを差し込めるようにしてあります。2011年の登場からNode.jsのテスト事情が何度も移り変わる中で、この「全部を抱え込まない」設計を変えずに使われ続けてきたのがMochaです。
Mochaとは
MochaはNode.js向けのテストフレームワークです。describeとitでテストをグループ化し、非同期処理を含むテストを読みやすく書けるようにすることを目的としています。テストは指定した順序どおりに実行され、失敗時のスタックトレースも正確に紐付けられます。
主な特徴
- アサーションライブラリを強制しない - Node標準の
assert、Chai、Should.js、Expect.jsなど、好きなアサーションスタイルを組み合わせられます - 複数のインターフェース - デフォルトの
describe/itによるBDDスタイルのほか、suite/testのTDDスタイル、exportsスタイル、QUnitスタイルも選べます - 非同期テストへの対応 - コールバックの
done、Promise、async/awaitのいずれの書き方でも非同期テストを扱えます - 柔軟なフック -
before・after・beforeEach・afterEachで、テストスイート単位・テスト単位のセットアップとクリーンアップを書き分けられます - 設定ファイルによる一元管理 -
.mocharc.ymlや.mocharc.jsonに実行オプションをまとめられ、CIとローカルで同じ挙動を再現できます - ブラウザでの実行にも対応 - Node.js専用ではなく、ブラウザ環境でテストを走らせる用途にも使われています
インストール
npmでプロジェクトに開発依存として追加します。
npm install --save-dev mocha
Yarnやpnpmを使っている場合は次のとおりです。
yarn add --dev mocha
pnpm add --save-dev mocha
アサーションライブラリとしてChaiも併用するのが定番の組み合わせです。
npm install --save-dev chai
基本的な使い方
testディレクトリにテストファイルを置き、describeでグループを作り、itで個々のテストケースを書きます。アサーションにはNode標準のassertモジュールを使うだけでも十分動きます。
// test/calculator.test.js
const assert = require('node:assert/strict')
const { add } = require('../src/calculator')
describe('add()', () => {
it('2つの正の数を足せる', () => {
assert.equal(add(2, 3), 5)
})
it('負の数を含んでも計算できる', () => {
assert.equal(add(-1, 1), 0)
})
})
実行はmochaコマンドから行います。package.jsonのscriptsに登録しておくと、npm testだけで呼び出せます。
{
"scripts": {
"test": "mocha"
}
}
npx mocha
# または
npm test
デフォルトではtestディレクトリ配下のファイルが対象になりますが、対象パスやパターンは後述の設定ファイルで自由に変更できます。
実践的なユースケース
非同期テストの書き方
APIクライアントやDBアクセスなど、実際のコードの多くは非同期です。Mochaはitのコールバックが返すPromiseを自動的に待ち受けるため、async/awaitをそのまま書くだけで非同期テストになります。
const assert = require('node:assert/strict')
const { fetchUser } = require('../src/user')
describe('fetchUser()', () => {
it('存在するIDならユーザー情報を返す', async () => {
const user = await fetchUser(1)
assert.equal(user.name, 'Alice')
})
it('存在しないIDならnullを返す', async () => {
const user = await fetchUser(-1)
assert.equal(user, null)
})
})
doneコールバックを使う古い書き方も引き続きサポートされていますが、Promiseベースのコードならasync/awaitの方がテストも読みやすくなります。
フックによるセットアップとティアダウン
DB接続やテスト用サーバーの起動など、複数のテストで共通の前処理・後処理が必要な場面は多くあります。Mochaのbefore・beforeEach・after・afterEachフックを使うと、この繰り返しをdescribeブロックの外に出せます。
const assert = require('node:assert/strict')
const { connect, disconnect, clearUsers, createUser } = require('../src/db')
describe('ユーザー登録', () => {
before(async () => {
await connect() // スイート開始時に1回だけ実行
})
beforeEach(async () => {
await clearUsers() // 各テストの前にデータをリセット
})
after(async () => {
await disconnect() // スイート終了時に1回だけ実行
})
it('新規ユーザーを登録できる', async () => {
const user = await createUser({ name: 'Bob' })
assert.equal(user.name, 'Bob')
})
})
beforeEachをテストごとの初期化に使い、before/afterを接続確立のような重い処理に使い分けるのが基本のパターンです。
TDDインターフェースへの切り替え
describe/itによるBDDスタイルがMochaのデフォルトですが、suite/testによるTDDスタイルに切り替えることもできます。チームの好みや既存コードの記法に合わせて選べるのは、複数インターフェースを標準サポートするMochaならではです。
// mocha --ui tdd で実行する
const assert = require('node:assert/strict')
const { multiply } = require('../src/calculator')
suite('multiply()', () => {
test('2つの数の積を返す', () => {
assert.equal(multiply(3, 4), 12)
})
test('0を掛けると0になる', () => {
assert.equal(multiply(5, 0), 0)
})
})
インターフェースはCLIの--uiオプション、または後述の設定ファイルで指定します。同じプロジェクト内でファイルごとに書き分けることも可能です。
.mocharcで実行オプションを一元管理する
対象ファイルのパターン、タイムアウト、レポーター、TypeScriptを使う場合のrequireフックなど、Mochaの実行オプションは多岐にわたります。毎回CLI引数で指定する代わりに、プロジェクトルートの.mocharc.ymlにまとめておくと、ローカルでもCIでも同じ設定でテストが実行されます。
# .mocharc.yml
spec: 'test/**/*.test.js'
timeout: 5000
reporter: spec
require:
- ts-node/register
.mocharc.jsonや.mocharc.cjsなど複数の形式に対応しているため、既存のプロジェクトの設定スタイルに合わせて選べます。CIで並列実行したい場合は--parallelオプションを付けることで、複数プロセスに分散してテストを実行することもできます。
まとめ
Mochaは、テストの実行とアサーションを分離するという一貫した設計のまま、長くNode.jsのテスト環境を支えてきたフレームワークです。BDD・TDDを含む複数のインターフェース、柔軟なフック、Promiseベースの非同期テストなど、必要な機能はひと通り揃っている一方で、「アサーションはどれを使うか」という選択を自分の手元に残してくれます。
すでにJestやVitestを使っている場合でも、既存のNode.jsプロジェクトにMochaが組み込まれているケースは少なくありません。まずはnode:assertだけで小さなテストを1本書いてみて、フックや設定ファイルへと段階的に手を広げていくのがおすすめです。