NodeJS 速習チュートリアル

Node.js Assert モジュール

1. Assert モジュールとは?

Assert モジュールは、コード内の不変条件(Invariants)を検証するための、シンプルかつ強力なアサーションテスト機能を提供します。
これは Node.js のコアモジュールであり、別途インストールする必要はありません。

主な機能は以下の通りです:

  • Truthy/Falsy な値のシンプルなアサーション
  • 厳密(Strict)および緩い(Loose)等価性チェック
  • オブジェクトのディープ比較(Deep Comparison)
  • エラーのスロー(Throw)とハンドリング
  • async/await パターンのサポート

       注意: Jest や Mocha のようなテスティングフレームワークほど多機能ではありませんが、Assert モジュールは非常に軽量であり、シンプルなテストのニーズや外部依存関係を避けたい場合に最適です。

2. Assert の導入

以下は、Assert モジュールを使用してシンプルな関数をテストするクイック例です。

2.1 基本的なアサーションの例

const assert = require('assert').strict;

// テスト対象の関数
function add(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new TypeError('入力値は数値である必要があります');
  }
  return a + b;
}

// テストケース
assert.strictEqual(add(2, 3), 5, '2 + 3 は 5 になるべきです');

// エラーケースのテスト
assert.throws(
  () => add('2', 3),
  TypeError,
  '数値以外の入力に対しては TypeError をスローすべきです'
);

console.log('すべてのテストをパスしました!');

3. インポートとセットアップ

Node.js アプリケーションで Assert モジュールをインポートして使用する方法はいくつかあります。

3.1 CommonJS によるインポート (Node.js)

// 基本的な require
const assert = require('assert');

// 厳密モード(推奨)の使用
const assert = require('assert').strict;

// 特定のメソッドをデストラクチャリング
const { strictEqual, deepStrictEqual, throws } = require('assert');

// 非同期テスト用
const { rejects, doesNotReject } = require('assert').strict;

3.2 ES Modules によるインポート (Node.js 12+)

// デフォルトインポートの使用
import assert from 'assert';

// ESM で厳密モードを使用する場合
import { strict as assert } from 'assert';

// 特定のメソッドをインポート
import { strictEqual, deepStrictEqual } from 'assert';

// ダイナミックインポート
const { strict: assert } = await import('assert');

       ベストプラクティス: より正確な比較と分かりやすいエラーメッセージを提供するため、厳密モード(Strict Mode)の使用が推奨されます。また、将来の Node.js バージョンでは厳密モードがデフォルトになる予定です。

4. コア・アサーションメソッド

Assert モジュールは、コード内の値についてアサーションを行うための複数のメソッドを提供します。これらのメソッドは、Assert モジュールを使用したテストの基礎となります。

4.1 assert(value[, message])

値が Truthy かどうかをテストします。値が Falsy な場合、AssertionError がスローされます。

const assert = require('assert');

// これらはパスします
assert(true);
assert(1);
assert('string');
assert({});

try {
  // これは AssertionError をスローします
  assert(false, 'この値は Truthy ではありません');
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

try {
  // これらもエラーをスローします
  assert(0);
  assert('');
  assert(null);
  assert(undefined);
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

4.2 assert.ok(value[, message])

これは assert() のエイリアスです。

const assert = require('assert');

// これらのアサーションは等価です
assert.ok(true, 'この値は Truthy です');
assert(true, 'この値は Truthy です');

5. 値の比較

Assert モジュールは、型変換(Coercion)やオブジェクト比較に関する挙動が異なる、複数の値比較方法を提供します。

5.1 assert.equal(actual, expected[, message])

等価演算子(==)を使用して、actualexpected の間の浅い(Shallow)、型変換を伴う等価性をテストします。

const assert = require('assert');

// これらはパスします(型変換を伴う等価性)
assert.equal(1, 1);
assert.equal('1', 1); // 文字列が数値に変換されます
assert.equal(true, 1); // 真偽値が数値に変換されます

try {
  // これはエラーをスローします
  assert.equal(1, 2, '1 は 2 と等しくありません');
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

5.2 assert.strictEqual(actual, expected[, message])

厳密等価演算子(===)を使用して、actualexpected の間の厳密な等価性をテストします。

const assert = require('assert');

// これはパスします
assert.strictEqual(1, 1);

try {
  // これらはエラーをスローします(厳密な等価性)
  assert.strictEqual('1', 1, '文字列の "1" は数値の 1 と厳密には等しくありません');
  assert.strictEqual(true, 1, 'true は 1 と厳密には等しくありません');
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

ベストプラクティス: 予期しない型変換の問題を避けるため、equal() よりも strictEqual() を使用することをお勧めします。

6. オブジェクトと配列の比較

オブジェクトや配列を扱う場合、参照だけでなく、その中身を比較するためにディープチェック(Deep Check)を行う必要があります。

6.1 assert.deepEqual(actual, expected[, message])

緩い等価性(==)を用いて、actualexpected の間のディープな等価性をテストします。

6.2 assert.deepStrictEqual(actual, expected[, message])

厳密な等価性(===)を用いて、actualexpected の間のディープな等価性をテストします。

const assert = require('assert');

// 同じ構造を持つオブジェクト
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };
const obj3 = { a: '1', b: { c: '2' } };

// これらはパスします
assert.deepEqual(obj1, obj2);
assert.deepStrictEqual(obj1, obj2);

// これはパスします(緩い等価性)
assert.deepEqual(obj1, obj3);

try {
  // これはエラーをスローします(厳密な等価性)
  assert.deepStrictEqual(obj1, obj3, 'オブジェクトが厳密にはディープ等価ではありません');
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

// 配列
const arr1 = [1, 2, [3, 4]];
const arr2 = [1, 2, [3, 4]];
const arr3 = ['1', '2', ['3', '4']];

// これらはパスします
assert.deepEqual(arr1, arr2);
assert.deepStrictEqual(arr1, arr2);

// これはパスします(緩い等価性)
assert.deepEqual(arr1, arr3);

try {
  // これはエラーをスローします(厳密な等価性)
  assert.deepStrictEqual(arr1, arr3, '配列が厳密にはディープ等価ではありません');
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

7. 不等価と否定

等価性をチェックするのと同様に、値が等しくないことを検証することも重要です。

7.1 assert.notEqual(actual, expected[, message])

不等価演算子(!=)を使用して、浅い型変換を伴う不等価性をテストします。

7.2 assert.notStrictEqual(actual, expected[, message])

厳密不等価演算子(!==)を使用して、厳密な不等価性をテストします。

const assert = require('assert');

// これらはパスします
assert.notEqual(1, 2);
assert.notStrictEqual('1', 1);

try {
  // これはエラーをスローします
  assert.notEqual(1, '1', '1 は "1" と型変換を含めると等しくなります');
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

try {
  // これはエラーをスローします
  assert.notStrictEqual(1, 1, '1 は 1 と厳密に等しいです');
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

7.3 ディープ不等価

  • assert.notDeepEqual(actual, expected[, message]):緩い不等価性でディープな不等価性をテストします。
  • assert.notDeepStrictEqual(actual, expected[, message]):厳密な不等価性でディープな不等価性をテストします。
const assert = require('assert');

const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 3 };
const obj3 = { a: '1', b: '2' };

// これらはパスします
assert.notDeepEqual(obj1, obj2);
assert.notDeepStrictEqual(obj1, obj2);
assert.notDeepStrictEqual(obj1, obj3);

try {
  // これはエラーをスローします(緩い等価性)
  assert.notDeepEqual(obj1, obj3, 'obj1 は obj3 と緩やかなディープ等価です');
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

8. エラーハンドリング

コードが期待通りのエラーをスローすることをテストすることは、堅牢なアプリケーションを作成する上で不可欠です。

8.1 assert.throws(fn[, error][, message])

関数 fn がエラーをスローすることを期待します。スローされない場合、AssertionError がスローされます。

const assert = require('assert');

// エラーをスローする関数
function throwingFunction() {
  throw new Error('エラー発生');
}

// これはパスします
assert.throws(throwingFunction);

// 特定のエラーメッセージを確認
assert.throws(
  throwingFunction,
  /エラー発生/,
  '予期しないエラーメッセージです'
);

// 特定のエラー型を確認
assert.throws(
  throwingFunction,
  Error,
  '間違ったエラータイプです'
);

// 検証関数による確認
assert.throws(
  throwingFunction,
  function(err) {
    return err instanceof Error && /発生/.test(err.message);
  },
  'エラーの検証に失敗しました'
);

try {
  // これは AssertionError をスローします
  assert.throws(() => {
    // この関数はスローしません
    return 'エラーなし';
  }, '関数がスローすることを期待していました');
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

8.2 assert.doesNotThrow(fn[, error][, message])

関数 fn がエラーをスローしないことを期待します。スローされた場合、そのエラーは伝播(プロパゲート)されます。

const assert = require('assert');

// これはパスします
assert.doesNotThrow(() => {
  return 'エラーなし';
});

try {
  // これは元のエラーをスローします
  assert.doesNotThrow(() => {
    throw new Error('これがスローされます');
  }, '予期しないエラー');
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

9. 非同期コードのテスト

現代の JavaScript では、非同期パターンが多用されます。Assert モジュールは、Promise ベースおよびコールバックベースの両方の非同期コードをテストするためのユーティリティを提供します。

9.1 assert.rejects(asyncFn[, error][, message])

asyncFn の Promise または非同期関数を待機し、それが拒否(Reject)されることを期待します。

const assert = require('assert');

async function asyncTest() {
  // 拒否される Promise を返す関数
  function failingAsyncFunction() {
    return Promise.reject(new Error('非同期エラー'));
  }

  // これはパスします
  await assert.rejects(
    failingAsyncFunction(),
    /非同期エラー/
  );

  // これもパスします
  await assert.rejects(
    async () => {
      throw new Error('非同期関数のエラー');
    },
    {
      name: 'Error',
      message: '非同期関数のエラー'
    }
  );

  try {
    // これは AssertionError をスローします
    await assert.rejects(
      Promise.resolve('成功'),
      'Promise が拒否されることを期待していました'
    );
  } catch (err) {
    console.error(`エラー: ${err.message}`);
  }
}

// 非同期テストを実行
asyncTest().catch(err => console.error(`未ハンドリングエラー: ${err.message}`));

9.2 assert.doesNotReject(asyncFn[, error][, message])

asyncFn の Promise または非同期関数を待機し、それが解決(Fulfill)されることを期待します。

const assert = require('assert');

async function asyncTest() {
  // これはパスします
  await assert.doesNotReject(
    Promise.resolve('成功')
  );

  // これもパスします
  await assert.doesNotReject(
    async () => {
      return '非同期関数の成功';
    }
  );

  try {
    // これは元の拒否理由(Rejection Reason)をスローします
    await assert.doesNotReject(
      Promise.reject(new Error('失敗')),
      'Promise が解決することを期待していました'
    );
  } catch (err) {
    console.error(`エラー: ${err.message}`);
  }
}

// 非同期テストを実行
asyncTest().catch(err => console.error(`未ハンドリングエラー: ${err.message}`));

10. その他のアサーションメソッド

10.1 assert.match(string, regexp[, message])

入力文字列が正規表現にマッチすることを期待します。

const assert = require('assert');

// これはパスします
assert.match('I love Node.js', /Node\.js/);

try {
  // これは AssertionError をスローします
  assert.match('Hello World', /Node\.js/, '文字列がパターンにマッチしません');
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

10.2 assert.fail([message])

指定されたメッセージ、またはデフォルトのメッセージで AssertionError をスローします。

const assert = require('assert');

try {
  // これは常に AssertionError をスローします
  assert.fail('このテストは常に失敗します');
} catch (err) {
  console.error(`エラー: ${err.message}`);
}

11. 厳密モード(Strict Mode)

Node.js は、すべての比較において厳密等価性を使用するアサーションの厳密モードを提供しています。予見可能な結果を得るために、厳密モードの使用が推奨されます。

// assert の厳密版をインポート
const assert = require('assert').strict;

// これらは等価です
assert.strictEqual(1, 1);
assert.equal(1, 1); // 厳密モードでは、これは strictEqual と同じです

// これらは等価です
assert.deepStrictEqual({ a: 1 }, { a: 1 });
assert.deepEqual({ a: 1 }, { a: 1 }); // 厳密モードでは、これは deepStrictEqual と同じです

try {
  // 厳密モードでは、これはエラーをスローします
  assert.equal('1', 1);
} catch (err) {
  console.error(`エラー: ${err.message}`);
}