🐧

【jest/vitest】toBe と toEqual の違い

2024/10/06に公開

JavaScriptで以下のようなobjectを返す関数を考えます。ManyKeyObjectとあるように、非常に多くのkeyを持つobjectを返します。

type ManyKeyObject = {
    a: string
    b: string
    ...
    z: string
};

const returnObject = ({
  object1,
  object2,
  isFirst,
}: {
  object1: ManyKeyObject;
  object2: ManyKeyObject;
  isFirst: boolean;
}) => {
  return isFirst ? object1 : object2;
};

returnObjectのテストを以下のように書くときに、toBeとtoEqualのどちらが適切でしょうか?ManyKeyObjectはkeyが非常に多いため、テストデータを作成せずにobject1とobject2のそれぞれが返されることをテストしたいです。

const object1 = {} as ManyKeyObject;
const object2 = {} as ManyKeyObject;

describe('returnObject', () => {
  describe('when isFirst is true', () => {
    it('returns object1', () => {
      // toBe or toEqual ?
      expect(returnObject({ object1, object2, isFirst: true })).toBe(object1);
      expect(returnObject({ object1, object2, isFirst: true })).toEqual(
        object1,
      );
    });
  });

  describe('when isFirst is false', () => {
    it('returns object2', () => {
      // toBe or toEqual ?
      expect(returnObject({ object1, object2, isFirst: false })).toBe(object2);
      expect(returnObject({ object1, object2, isFirst: false })).toEqual(
        object2,
      );
    });
  });
});

正解はこちら
describe('returnObject', () => {
  describe('when isFirst is true', () => {
    it('returns object1', () => {
      expect(returnObject({ object1, object2, isFirst: true })).toBe(object1);
    });
  });

  describe('when isFirst is false', () => {
    it('returns object2', () => {
      expect(returnObject({ object1, object2, isFirst: false })).toBe(object2);
    });
  });
});

のようにtoBeになります。

それぞれの違いについて見ていきましょう。

toBe

プリミティブが等しいか、もしくはobjectの参照が同じかを検証します。Object.isの結果と同等になります。

今回、object1とobject2は同じ構造であり、object1が返されることをテストしたいため、objectの参照を検証するtoBeを使用するのが適切でした。

toEqual

実際の値が同じか、objectの場合は再帰的に同じ構造かどうかを検証します。

describe('toEqual', () => {
  it('works', () => {
    expect({ a: 1 }).toEqual({ a: 1 });
    expect({
      a: {
        b: 1,
      },
    }).toEqual({
      a: {
        b: 1,
      },
    });
  });
});

冒頭の例では、object1とobject2は同じ構造となり、object1とobject2のどちらと比較してもテストが通ってしまうので、toEqualを使用するのは適切ではありませんでした。

まとめ

toBeとtoEqualはそれぞれ以下のように使い分けができます。

  • toBe
    • プリミティブの検証
    • objectの参照が同じかの検証
  • toEqual
    • objectの値が同じかの検証

番外編

toBeCloseTo

浮動小数点数の比較に使用されます。

describe('toBe', () => {
  it('works', () => {
    expect(0.2 + 0.1).toBe(0.3);
  });
});

のようなテストをした場合、

    Expected: 0.3
    Received: 0.30000000000000004

とテストが落ちてしまうので、toBeCloseToでテストしたい桁数を指定することができます。

describe('toBeCloseTo', () => {
  it('works', () => {
    // Passed!!!!
    expect(0.2 + 0.1).toBeCloseTo(0.3, 5);
  });
});

toStrictEqual

toEqualとは基本的に同じです。以下の違いがあります。

  • undefinedとなるkeyを持つobjectかどうかをチェックします
describe('toStrictEqual', () => {
  it('works', () => {
    expect({ a: 1, b: undefined }).toEqual({ a: 1 });
    expect({ a: 1, b: undefined }).not.toStrictEqual({ a: 1 });
  });
});
  • 配列にundefinedが含まれるかどうかをチェックします
describe('toStrictEqual', () => {
  it('works', () => {
    expect([1, undefined]).toEqual([1]);
    expect([1, undefined]).not.toStrictEqual([1]);
  });
});
  • objectのtypeが等しいかどうかをチェックします
    例えば、フィールドabを持つクラスのインスタンスは、フィールドabを持つobjectと等しくなりません。
class Stock {
  constructor(type) {
    this.type = type;
  }
}

describe('toStrictEqual', () => {
  test('structurally the same, but semantically different', () => {
    expect(new Stock('apples')).toEqual({ type: 'apples' });
    expect(new Stock('apples')).not.toStrictEqual({ type: 'apples' });
  });
});

参考

Discussion