noxi雑記

.NET、Angularまわりの小ネタブログ

JasmineのテストでMoment.jsをイイ感じに比較する

JavaScriptの日付ライブラリといえばMoment.js。標準のDateと比較するととても便利です。ただMoment.jsは内部のデータの持ち方が複雑で、ユニットテストで度々問題になります(私はなりました)。
Jasmineでユニットテストを記述するときにイイ感じに比較する方法を紹介します。


JasmineのaddCustomEqualityTesterを使用して一致判定をカスタマイズする

Jasmine標準の toEqual を使うだけでもオブジェクトの広い範囲で一致判定させることができますが、Jasmineには一致判定される対象をカスタマイズする機能が備わっています。 addCustomEqualityTester で一致判定を行う関数を追加すると、任意の対象を等しいと判定させることができます。

標題の通りMoment.jsを一致判定させてみます。まずは必ずコケるMoment.jsのテスト例です。

import * as moment from 'moment';

it('Moment.js equality', () => {
  expect(moment('2019-01-01T01:02:03.123')).not.toEqual('2019-01-01T01:02:03.123' as any);
  expect(moment('2019-01-01T01:02:03.123')).not.toEqual(new Date('2019-01-01T01:02:03.123') as any);
  expect(moment('2019-01-01T01:02:03.123')).toEqual(moment([2019, 0, 1, 1, 2, 3, 123]));
});

このテストを実行すると3個目のexpectがNG判定となります。

Error: Expected object not to have properties
    _f: 'YYYY-MM-DDTHH:mm:ss.SSSS'
    at <Jasmine>
    at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/test.ts:40:45)
    at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:359:1)
    at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:308:1)

ここに addCustomEqualityTester でMoment.jsを一致させるコードを追加します。先ほどはコケていたテストコードが無事通過するようになります。

import { isMoment } from 'moment';
import * as moment from 'moment';

beforeAll(() => {
  jasmine.addCustomEqualityTester((a, b) => {
    if (!isMoment(a) || !isMoment(b)) {
      // 両者ともMoment.jsでは無いので検証対象外 => undefinedを返す
      return;
    }

    // どちらかがMoment.jsなので確実にtrue/falseを返す
    return isMoment(a) && a.isSame(b);
  });
});

it('Moment.js equality', () => {
  expect(moment('2019-01-01T01:02:03.123')).not.toEqual('2019-01-01T01:02:03.123' as any);
  expect(moment('2019-01-01T01:02:03.123')).not.toEqual(new Date('2019-01-01T01:02:03.123') as any);
  expect(moment('2019-01-01T01:02:03.123')).toEqual(moment([2019, 0, 1, 1, 2, 3, 123]));
});

addCustomEqualityTesterbeforeAll beforeEach it のいずれかのスコープ内で記述する必要がある点にだけ注意してください。