あなたが使ったことのあるテストフレームワークは、Jest、Mocha、node:test のどれであっても、シンプルな考え方に基づいています。それは、「期待するものを記述し、現実がそれと異なる場合はエラーをスローする」というものです。Node.js はこの考え方を assert と呼ばれる組み込みモジュールとして提供しています。インストールも依存関係も不要で、ただ require するだけで仮定のチェックを始められます。
assert モジュールは単独で知る価値があります。これはスクリプトでの迅速な健全性チェックを可能にし、多くのテストランナーの基盤となっており、フレームワークが装飾する前にアサーションが実際に何であるかを教えてくれます。このガイドでは、重要なメソッド、人々を混乱させる厳密モードとレガシーモードの区別、エラーと非同期コードに対してアサーションを行う方法、そして同じモジュールが API レスポンスの検証にどのように役立つかについて説明します。
assert モジュールとは何か
アサーションとは、プログラムが正しいと見なされるために真でなければならない記述です。assert.strictEqual(total, 100) と記述する場合、total が 100 に等しいはずだと宣言しています。等しい場合、何も起こらず実行は続行されます。等しくない場合、assert は実行を停止し、何が問題だったかを伝える AssertionError をスローします。
モジュールをインポートします。最新の推奨される形式は厳密バージョンです。
const assert = require('node:assert/strict');
// or with ES modules:
// import assert from 'node:assert/strict';
最もシンプルなアサーションは、値が truthy であることを確認します。
const user = getUser(42);
assert(user, 'getUser should return a user object');
最初の引数はテスト対象の値です。2番目の引数(オプション)は、アサーションが失敗したときに表示されるメッセージです。このメッセージは常に記述するようにしてください。「getUser should return a user object」というメッセージは、単なる AssertionError よりもはるかに役立ちます。アサーションを理解することは、テストツールで専用の API アサーションに移行する際にも役立ちます。
厳密モードとレガシーモード
これは、最も正しく理解すべき重要な点です。assert モジュールには2つのモードがあります。
require('node:assert') で取得できるレガシーモードは、assert.equal と assert.deepEqual に緩やかな等価性 (==) を使用します。つまり、`1 == '1'` が JavaScript で真であるため、assert.equal(1, '1') は成功します。緩やかな等価性は、バグのよく知られた原因です。
require('node:assert/strict') で取得できる厳密モードは、すべてに厳密な等価性 (===) を使用します。assert.equal(1, '1') は、型が異なるため、当然失敗します。
const looseAssert = require('node:assert');
looseAssert.equal(1, '1'); // passes, surprising and dangerous
const strict = require('node:assert/strict');
strict.equal(1, '1'); // throws AssertionError, correct
厳密モードを使用してください。テストで緩やかな等価性を許容する正当な理由はありません。このガイドの残りの部分では、assert.equal が assert.strictEqual と同じように動作する node:assert/strict を前提としています。
値の比較: equal と strictEqual
assert.strictEqual(actual, expected) は、2つの値が === を使用して同一であることを確認します。これは数値、文字列、ブール値のようなプリミティブ型にとっての主力です。
const assert = require('node:assert/strict');
function priceWithTax(price, rate) {
return price + price * rate;
}
assert.strictEqual(priceWithTax(100, 0.08), 108, 'tax calc should add 8 percent');
assert.strictEqual(typeof priceWithTax(100, 0.08), 'number', 'result should be a number');
assert.notStrictEqual もあり、2つの値が同一でない場合に成功します。値が変更されたことを確認するために使用します。
const before = getCacheKey();
refreshCache();
const after = getCacheKey();
assert.notStrictEqual(before, after, 'cache key should change after refresh');
オブジェクトや配列の場合、strictEqual は役に立ちません。同じ内容を持つ2つのオブジェクトリテラルは異なる参照であるため、=== は false を返します。それがディープ等価性の目的です。
オブジェクトの比較: deepStrictEqual
assert.deepStrictEqual(actual, expected) は、構造と値を再帰的に比較します。すべてのキーが厳密に等しい値を保持している場合、2つのオブジェクトは成功と見なされます。
const assert = require('node:assert/strict');
function buildOrder(id, items) {
return { id, items, status: 'pending' };
}
assert.deepStrictEqual(
buildOrder(7, ['keyboard', 'mouse']),
{ id: 7, items: ['keyboard', 'mouse'], status: 'pending' },
'order object should match expected shape'
);
これは、オブジェクトを返す関数をテストする際、特に JSON ボディがオブジェクトである API レスポンスをテストする際に最もよく使用するメソッドです。その対となる assert.notDeepStrictEqual は、構造が異なる場合に成功します。
一つ注意点があります。deepStrictEqual は型もチェックします。数値の 7 を保持するプロパティは、文字列の '7' を保持するプロパティとは一致しません。この厳密さは機能であり、データの型ずれを検出します。多くの入力組み合わせを持つ関数をテストしている場合、CSV および JSON を使用したデータ駆動型テストに関するガイドでは、データセット全体でアサーションをスケールする方法を示しています。
deepStrictEqual が失敗すると、Node はどのプロパティが異なるかを正確にハイライト表示する差分を出力します。この差分は、このモジュールで最も役立つ機能の1つです。2つの大きなオブジェクトを凝視して違いを見つけようとする代わりに、ハイライトされた数行を読みます。ネストされたデータの場合、これだけでも各フィールドを手動で strictEqual でチェックするよりも deepStrictEqual を使用する価値があります。
assert.match と assert.ok を使用した部分一致
すべてのチェックで完全な等価性が必要なわけではありません。時には、値がおおよそ正しいかどうかだけを気にする場合があります。assert.ok(value) は、値が truthy である場合に常に成功します。これは「これが存在するはずだ」というチェックに適切なツールです。例えば、空でない文字列、定義されたオブジェクト、ゼロでないカウントなどです。
assert.match(string, regexp) は文字列が正規表現に一致するかどうかをチェックし、assert.doesNotMatch はその逆をチェックします。これらは、正確な内容が変化するが形状が固定されている値に役立ちます。
const assert = require('node:assert/strict');
function generateOrderId() {
return 'ORD-' + Date.now();
}
const id = generateOrderId();
// the exact timestamp changes, so assert the format, not the value
assert.match(id, /^ORD-\d+$/, 'order id should be ORD- followed by digits');
assert.ok(id.length > 4, 'order id should not be empty');
このパターンは、特に API テストで重要です。API レスポンスには、予測できないタイムスタンプ、生成された ID、トークンが含まれるためです。match でフォーマットをアサートし、ok で存在をアサートし、実際に制御できる値には strictEqual を使用します。
コードがエラーをスローすることのアサート
正しい動作がエラーをスローすることを意味する場合もあります。assert.throws(fn, expectedError) は関数を実行し、エラーをスローした場合にのみ成功します。
const assert = require('node:assert/strict');
function parsePort(value) {
const port = Number(value);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new RangeError('port must be an integer between 1 and 65535');
}
return port;
}
// passes: invalid input should throw a RangeError
assert.throws(
() => parsePort('70000'),
RangeError,
'out-of-range port should throw RangeError'
);
// you can also match the error message with a regex
assert.throws(
() => parsePort('abc'),
/must be an integer/,
'non-numeric port should throw a descriptive error'
);
// passes: valid input should not throw
assert.doesNotThrow(
() => parsePort('8080'),
'a valid port should not throw'
);
関数を渡すのであって、呼び出しを渡すのではないことに注意してください。assert.throws(parsePort('70000')) は、assert がそれを見る前に parsePort を実行してしまい、エラーは捕捉されずに逃げてしまいます。常に () => parsePort('70000') のようにラップしてください。
非同期コードのアサート: rejects
現代の Node.js コードは Promise でいっぱいで、拒否された Promise はスローされたエラーの非同期版に相当します。assert.rejects と assert.doesNotReject がそのケースを処理します。どちらも Promise を返すため、await する必要があります。
const assert = require('node:assert/strict');
async function fetchUser(id) {
if (typeof id !== 'number') {
throw new TypeError('id must be a number');
}
// ... real lookup would go here
return { id, name: 'Dana Lee' };
}
async function runTests() {
// passes: bad input rejects with a TypeError
await assert.rejects(
fetchUser('not-a-number'),
TypeError,
'string id should reject'
);
// passes: good input resolves without rejecting
await assert.doesNotReject(
fetchUser(101),
'valid id should resolve cleanly'
);
}
runTests();
assert.rejects の呼び出しを await し忘れるのはよくある間違いです。await がないと、テスト関数はアサーションが完了する前に終了し、失敗は明確なテストエラーではなく、ハンドルされない拒否となってしまいます。
assert を使用した API レスポンスのテスト
assert モジュールは、HTTP リクエストの出力をチェックするのに本当に役立ちます。エンドポイントをフェッチした後、ステータスコードと JSON ボディが得られ、どちらもアサートの対象となります。
const assert = require('node:assert/strict');
async function testGetUser() {
const res = await fetch('https://api.example.com/users/101');
// check the status code
assert.strictEqual(res.status, 200, 'GET /users/101 should return 200');
const body = await res.json();
// check the shape and types of the response
assert.strictEqual(typeof body.id, 'number', 'id should be a number');
assert.strictEqual(body.id, 101, 'id should match the requested user');
assert.ok(body.name, 'response should include a name');
assert.ok(Array.isArray(body.roles), 'roles should be an array');
}
testGetUser().then(
() => console.log('API test passed'),
(err) => { console.error('API test failed:', err.message); process.exitCode = 1; }
);
このパターンは機能し、基礎を教えてくれます。しかし、実際の API テストスイートでは、生の assert が提供しない構造化された実行、リトライ、環境処理、レポートが必要になります。自動テストスクリプトの書き方とpytest API 自動化フレームワークに関するガイドで、次のステップを解説しています。
テストハーネスを手作業で構築したくない場合は、Apidog を使用すると、アサーションコードを一切書かずに、API リクエストを定義し、ステータスコード、ヘッダー、JSON フィールドに対する視覚的なアサーションを添付できます。リクエストを自動テストシナリオに連結し、CI/CD で実行できます。また、assert が提供する制御が必要な場合には、カスタムスクリプトに移行することもできます。これは、「エンドポイントがある」から「テストスイートがある」までのより速い道筋であり、Apidog をダウンロードして無料で利用できます。Apidog と Node.js の assert モジュールを組み合わせることで、クイックチェックと完全なスイートの両方をカバーできます。
よくある質問
assert モジュールをインストールする必要がありますか?
いいえ。assert は Node.js に組み込まれています。require('node:assert/strict') または require('node:assert') でインポートします。インストールする npm パッケージも、管理する依存関係もありません。node: プレフィックスは、コアモジュールをロードしていることを明示的に示します。
assert.equal と assert.strictEqual の違いは何ですか?
レガシーモードでは、assert.equal は緩やかな等価性 (==) を使用し、assert.strictEqual は厳密な等価性 (===) を使用します。node:assert/strict を介してロードされる厳密モードでは、assert.equal は assert.strictEqual と同じように動作します。型の強制がテストをサイレントに通過させないように、常に厳密モードを使用してください。
strictEqual の代わりに deepStrictEqual をいつ使用すべきですか?
プリミティブ型(数値、文字列、ブール値)には strictEqual を使用します。オブジェクトや配列には deepStrictEqual を使用してください。なぜなら strictEqual は参照を比較し、2つの異なるオブジェクトは決して参照的に等しくならないからです。JSON ボディや返されたオブジェクトに対してアサートを行う際は、常に deepStrictEqual を使用してください。
assert モジュールと Jest のようなテストフレームワークのどちらを使用すべきですか?
ちょっとしたスクリプトや学習には、生の assert で十分です。本格的なテストスイートには、フレームワークを使用してください。Node.js には組み込みのテストランナーである node:test も同梱されており、assert と自然に連携し、外部依存なしでテストの整理、レポート作成、並列実行を提供します。
非同期関数が拒否されることをアサートするにはどうすればよいですか?
assert.rejects を使用し、忘れずに await してください。Promise または非同期関数呼び出しを渡し、オプションで期待されるエラーの型やメッセージの正規表現を指定できます。assert.rejects(somePromise, TypeError) は、Promise が TypeError で拒否された場合にのみ成功します。await がないと、失敗は明確なアサーションエラーではなく、未処理の拒否となってしまいます。
