メインコンテンツへスキップ
バージョン: 次へ

Expect

非公式ベータ版翻訳

このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →

テストを書く際には、値が特定の条件を満たしているか確認する必要がよくあります。expectを使うと、様々な検証が可能な「マッチャー」にアクセスできます。

ヒント

Jestコミュニティが管理する追加のマッチャーについては、jest-extendedを参照してください。

非公式ベータ版翻訳

このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →

情報

このページのTypeScriptの例は、JestのAPIを明示的にインポートした場合にのみ、ドキュメント通りに動作します:

import {expect, jest, test} from '@jest/globals';

TypeScriptでJestを設定する方法の詳細については、はじめにガイドを参照してください。

リファレンス


Expect

expect(value)

expect関数は値をテストする際に毎回使用します。単独でexpectを呼び出すことは稀で、通常はexpectを「マッチャー」関数と組み合わせて値に関するアサーションを行います。

具体例で理解しましょう。bestLaCroixFlavor()メソッドが文字列'grapefruit'を返すはずの場合、次のようにテストします:

test('the best flavor is grapefruit', () => {
expect(bestLaCroixFlavor()).toBe('grapefruit');
});

この例ではtoBeがマッチャー関数です。様々なテストシナリオに対応するため、多数のマッチャー関数が用意されており、以下で解説します。

expectの引数にはテスト対象のコードが生成する値を渡し、マッチャーの引数には期待される正しい値を渡します。これらを逆にするとテストは動作しますが、失敗時のエラーメッセージが不自然になります。

修飾子

.not

テスト方法が分かっている場合、.notを使うと逆の条件をテストできます。例えば以下は、La Croixの最高のフレーバーがココナッツでないことをテストします:

test('the best flavor is not coconut', () => {
expect(bestLaCroixFlavor()).not.toBe('coconut');
});

.resolves

resolvesは履行されたPromiseの値をアンラップし、他のマッチャーをチェーンできるようにします。Promiseが拒否されるとアサーションは失敗します。

例えば以下のコードは、Promiseが解決され結果が'lemon'になることをテストします:

test('resolves to lemon', () => {
// make sure to add a return statement
return expect(Promise.resolve('lemon')).resolves.toBe('lemon');
});
メモ

Promiseをテストしているため、テストは非同期のままです。したがって、アンラップされたアサーションを返すことでJestに待機するよう指示する必要があります。

代わりにasync/await.resolvesを組み合わせることも可能です:

test('resolves to lemon', async () => {
await expect(Promise.resolve('lemon')).resolves.toBe('lemon');
await expect(Promise.resolve('lemon')).resolves.not.toBe('octopus');
});

.rejects

.rejectsは拒否されたPromiseの理由をアンラップし、他のマッチャーをチェーンできるようにします。Promiseが履行されるとアサーションは失敗します。

例えば以下のコードは、Promiseが理由'octopus'で拒否されることをテストします:

test('rejects to octopus', () => {
// make sure to add a return statement
return expect(Promise.reject(new Error('octopus'))).rejects.toThrow(
'octopus',
);
});
メモ

Promiseをテストしているため、テストは非同期のままです。したがって、アンラップされたアサーションを返すことでJestに待機するよう指示する必要があります。

代わりにasync/await.rejectsを組み合わせることも可能です:

test('rejects to octopus', async () => {
await expect(Promise.reject(new Error('octopus'))).rejects.toThrow('octopus');
});

マッチャー

.toBe(value)

プリミティブ値の比較やオブジェクトインスタンスの参照同一性チェックに.toBeを使用します。内部でObject.isを呼び出して値を比較するため、===厳密等価演算子よりもテスト向きです。

例えば以下のコードはcanオブジェクトのプロパティを検証します:

const can = {
name: 'pamplemousse',
ounces: 12,
};

describe('the can', () => {
test('has 12 ounces', () => {
expect(can.ounces).toBe(12);
});

test('has a sophisticated name', () => {
expect(can.name).toBe('pamplemousse');
});
});

浮動小数点数には.toBeを使用しないでください。例えばJavaScriptでは丸め誤差により0.2 + 0.10.3と厳密に等しくありません。浮動小数点数を扱う場合は、代わりに.toBeCloseToの使用を検討してください。

.toBeマッチャーは参照の同一性を検証しますが、アサーションが失敗した場合には値の詳細な比較結果を報告します。プロパティの差異がテスト失敗の理由を理解するのに役立たない場合、特にレポートが大規模なときは、比較処理をexpect関数内に移動することを検討してください。たとえば、要素が同じインスタンスかどうかをアサートする場合:

  • expect(received).toBe(expected)expect(Object.is(received, expected)).toBe(true) に書き換える

  • expect(received).not.toBe(expected)expect(Object.is(received, expected)).toBe(false) に書き換える

.toHaveBeenCalled()

モック関数が呼び出されたことを確認するには.toHaveBeenCalledを使用します。

例:drinkAll(drink, flavour)関数(drink関数を全ての飲み物に適用)があるとします。drinkが呼び出されることを確認するテストスイートは次のようになります:

function drinkAll(callback, flavour) {
if (flavour !== 'octopus') {
callback(flavour);
}
}

describe('drinkAll', () => {
test('drinks something lemon-flavoured', () => {
const drink = jest.fn();
drinkAll(drink, 'lemon');
expect(drink).toHaveBeenCalled();
});

test('does not drink something octopus-flavoured', () => {
const drink = jest.fn();
drinkAll(drink, 'octopus');
expect(drink).not.toHaveBeenCalled();
});
});

.toHaveBeenCalledTimes(number)

モック関数が正確な回数だけ呼び出されたことを確認するには.toHaveBeenCalledTimesを使用します。

例:drinkEach(drink, Array<flavor>)関数(drink関数を飲み物の配列に適用)があるとします。drink関数が正確な回数呼び出されることを確認するテストスイートは次のようになります:

test('drinkEach drinks each drink', () => {
const drink = jest.fn();
drinkEach(drink, ['lemon', 'octopus']);
expect(drink).toHaveBeenCalledTimes(2);
});

.toHaveBeenCalledWith(arg1, arg2, ...)

モック関数が特定の引数で呼び出されたことを確認するには.toHaveBeenCalledWithを使用します。引数のチェックには.toEqualと同じアルゴリズムが使われます。

例:飲み物を登録するregister関数があり、applyToAll(f)が登録済みの全飲み物に関数fを適用するとします。これを検証するテストは次のように書けます:

test('registration applies correctly to orange La Croix', () => {
const beverage = new LaCroix('orange');
register(beverage);
const f = jest.fn();
applyToAll(f);
expect(f).toHaveBeenCalledWith(beverage);
});

.toHaveBeenLastCalledWith(arg1, arg2, ...)

モック関数が最後に呼び出された時の引数をテストするには.toHaveBeenLastCalledWithを使用します。例:applyToAllFlavors(f)関数(複数のフレーバーにfを適用)があり、最後に操作されるフレーバーが'mango'であることを確認する場合:

test('applying to all flavors does mango last', () => {
const drink = jest.fn();
applyToAllFlavors(drink);
expect(drink).toHaveBeenLastCalledWith('mango');
});

.toHaveBeenNthCalledWith(nthCall, arg1, arg2, ....)

モック関数がn回目に呼び出された時の引数をテストするには.toHaveBeenNthCalledWithを使用します。例:drinkEach(drink, Array<flavor>)関数があり、これは f を複数のフレーバーに適用する関数です。1番目に操作されるフレーバーが'lemon'、2番目が'octopus'であることを確認する場合:

test('drinkEach drinks each drink', () => {
const drink = jest.fn();
drinkEach(drink, ['lemon', 'octopus']);
expect(drink).toHaveBeenNthCalledWith(1, 'lemon');
expect(drink).toHaveBeenNthCalledWith(2, 'octopus');
});
メモ

n回目の引数は1から始まる正の整数でなければなりません。

.toHaveReturned()

モック関数が少なくとも1回は正常に戻り値(=エラーをスローせず)を返したことをテストするには.toHaveReturnedを使用します。例:trueを返すモックdrinkがある場合:

test('drinks returns', () => {
const drink = jest.fn(() => true);

drink();

expect(drink).toHaveReturned();
});

.toHaveReturnedTimes(number)

モック関数が正確な回数だけ正常に戻り値(=エラーをスローせず)を返したことを確認するには.toHaveReturnedTimesを使用します。エラーをスローした呼び出しは正常な戻り値の回数に含まれません。

例:trueを返すモックdrinkがある場合:

test('drink returns twice', () => {
const drink = jest.fn(() => true);

drink();
drink();

expect(drink).toHaveReturnedTimes(2);
});

.toHaveReturnedWith(value)

モック関数が特定の値を返したことを確認するには.toHaveReturnedWithを使用します。

例えば、消費された飲み物の名前を返すモック関数 drink がある場合、次のように記述できます:

test('drink returns La Croix', () => {
const beverage = {name: 'La Croix'};
const drink = jest.fn(beverage => beverage.name);

drink(beverage);

expect(drink).toHaveReturnedWith('La Croix');
});

.toHaveLastReturnedWith(value)

.toHaveLastReturnedWith は、モック関数が最後に返した特定の値をテストするために使用します。モック関数の最後の呼び出しでエラーが発生した場合、期待する戻り値に関係なくこのマッチャーは失敗します。

例えば、消費された飲み物の名前を返すモック関数 drink がある場合、次のように記述できます:

test('drink returns La Croix (Orange) last', () => {
const beverage1 = {name: 'La Croix (Lemon)'};
const beverage2 = {name: 'La Croix (Orange)'};
const drink = jest.fn(beverage => beverage.name);

drink(beverage1);
drink(beverage2);

expect(drink).toHaveLastReturnedWith('La Croix (Orange)');
});

.toHaveNthReturnedWith(nthCall, value)

.toHaveNthReturnedWith は、モック関数がn回目の呼び出しで返した特定の値をテストするために使用します。n回目の呼び出しでエラーが発生した場合、期待する戻り値に関係なくこのマッチャーは失敗します。

例えば、消費された飲み物の名前を返すモック関数 drink がある場合、次のように記述できます:

test('drink returns expected nth calls', () => {
const beverage1 = {name: 'La Croix (Lemon)'};
const beverage2 = {name: 'La Croix (Orange)'};
const drink = jest.fn(beverage => beverage.name);

drink(beverage1);
drink(beverage2);

expect(drink).toHaveNthReturnedWith(1, 'La Croix (Lemon)');
expect(drink).toHaveNthReturnedWith(2, 'La Croix (Orange)');
});
メモ

n回目の引数は1から始まる正の整数でなければなりません。

.toHaveLength(number)

.toHaveLength は、オブジェクトが .length プロパティを持ち、その値が特定の数値に設定されていることを確認するために使用します。

配列や文字列のサイズを確認する場合に特に便利です。

expect([1, 2, 3]).toHaveLength(3);
expect('abc').toHaveLength(3);
expect('').not.toHaveLength(5);

.toHaveProperty(keyPath, value?)

.toHaveProperty は、オブジェクトの指定された参照パス keyPath にプロパティが存在するかどうかを確認します。深くネストされたプロパティを確認するには、ドット記法 または深い参照用のキーパスを含む配列を使用できます。

オプションの value 引数を指定すると、受信したプロパティ値を比較できます(toEqual マッチャーと同様に、オブジェクトインスタンスのすべてのプロパティを再帰的に比較する深い等価性チェックが行われます)。

次の例は、ネストされたプロパティを持つ houseForSale オブジェクトを含んでいます。toHaveProperty を使用して、オブジェクト内のさまざまなプロパティの存在と値を確認しています。

// Object containing house features to be tested
const houseForSale = {
bath: true,
bedrooms: 4,
kitchen: {
amenities: ['oven', 'stove', 'washer'],
area: 20,
wallColor: 'white',
'nice.oven': true,
},
livingroom: {
amenities: [
{
couch: [
['large', {dimensions: [20, 20]}],
['small', {dimensions: [10, 10]}],
],
},
],
},
'ceiling.height': 2,
};

test('this house has my desired features', () => {
// Example Referencing
expect(houseForSale).toHaveProperty('bath');
expect(houseForSale).toHaveProperty('bedrooms', 4);

expect(houseForSale).not.toHaveProperty('pool');

// Deep referencing using dot notation
expect(houseForSale).toHaveProperty('kitchen.area', 20);
expect(houseForSale).toHaveProperty('kitchen.amenities', [
'oven',
'stove',
'washer',
]);

expect(houseForSale).not.toHaveProperty('kitchen.open');

// Deep referencing using an array containing the keyPath
expect(houseForSale).toHaveProperty(['kitchen', 'area'], 20);
expect(houseForSale).toHaveProperty(
['kitchen', 'amenities'],
['oven', 'stove', 'washer'],
);
expect(houseForSale).toHaveProperty(['kitchen', 'amenities', 0], 'oven');
expect(houseForSale).toHaveProperty(
'livingroom.amenities[0].couch[0][1].dimensions[0]',
20,
);
expect(houseForSale).toHaveProperty(['kitchen', 'nice.oven']);
expect(houseForSale).not.toHaveProperty(['kitchen', 'open']);

// Referencing keys with dot in the key itself
expect(houseForSale).toHaveProperty(['ceiling.height'], 'tall');
});

.toBeCloseTo(number, numDigits?)

浮動小数点数を近似的に比較するには toBeCloseTo を使用します。

オプションの numDigits 引数は、小数点以下のチェック桁数を制限します。デフォルト値 2 の場合、テスト基準は Math.abs(expected - received) < 0.005(つまり 10 ** -2 / 2)となります。

直感的な等価比較はしばしば失敗します。なぜなら、10進数(base 10)値の算術演算は、限られた精度の2進数(base 2)表現では丸め誤差が発生するためです。例えば、このテストは失敗します:

test('adding works sanely with decimals', () => {
expect(0.2 + 0.1).toBe(0.3); // Fails!
});

JavaScript では 0.2 + 0.1 が実際には 0.30000000000000004 となるため失敗します。

例えば5桁の精度で検証するテスト:

test('adding works sanely with decimals', () => {
expect(0.2 + 0.1).toBeCloseTo(0.3, 5);
});

toBeCloseTo は浮動小数点エラーの解決を目的としているため、大きな整数値はサポートしていません。

.toBeDefined()

変数が undefined でないことを確認するには .toBeDefined を使用します。例えば、関数 fetchNewFlavorIdea() が何かを返すことを確認したい場合:

test('there is a new flavor idea', () => {
expect(fetchNewFlavorIdea()).toBeDefined();
});

expect(fetchNewFlavorIdea()).not.toBe(undefined) と書くこともできますが、コード内で直接 undefined を参照しない方が良いプラクティスです。

.toBeFalsy()

値が何であるかを気にせず、真偽値コンテキストで値が偽であることを確認したい場合に .toBeFalsy を使用します。例えば、次のようなアプリケーションコードがあるとします:

drinkSomeLaCroix();
if (!getErrors()) {
drinkMoreLaCroix();
}

具体的に getErrors が何を返すか気にする必要はありません。falsenull0 などを返す可能性がありますが、コードは正常に動作します。したがって、La Croix を飲んだ後にエラーがないことをテストしたい場合、次のように記述できます:

test('drinking La Croix does not lead to errors', () => {
drinkSomeLaCroix();
expect(getErrors()).toBeFalsy();
});

JavaScript には 6 つの偽値があります: false, 0, '', null, undefined, NaN です。これら以外の値はすべて真値です。

.toBeGreaterThan(number | bigint)

数値または big integer 値に対して received > expected を比較するには toBeGreaterThan を使用します。たとえば、ouncesPerCan() が 10 オンスより大きい値を返すことをテストするには:

test('ounces per can is more than 10', () => {
expect(ouncesPerCan()).toBeGreaterThan(10);
});

.toBeGreaterThanOrEqual(number | bigint)

数値または big integer 値に対して received >= expected を比較するには toBeGreaterThanOrEqual を使用します。たとえば、ouncesPerCan() が少なくとも 12 オンスの値を返すことをテストするには:

test('ounces per can is at least 12', () => {
expect(ouncesPerCan()).toBeGreaterThanOrEqual(12);
});

.toBeLessThan(number | bigint)

数値または big integer 値に対して received < expected を比較するには toBeLessThan を使用します。たとえば、ouncesPerCan() が 20 オンス未満の値を返すことをテストするには:

test('ounces per can is less than 20', () => {
expect(ouncesPerCan()).toBeLessThan(20);
});

.toBeLessThanOrEqual(number | bigint)

数値または big integer 値に対して received <= expected を比較するには toBeLessThanOrEqual を使用します。たとえば、ouncesPerCan() が最大 12 オンスの値を返すことをテストするには:

test('ounces per can is at most 12', () => {
expect(ouncesPerCan()).toBeLessThanOrEqual(12);
});

.toBeInstanceOf(Class)

オブジェクトがクラスのインスタンスであることを確認するには .toBeInstanceOf(Class) を使用します。このマッチャーは内部で instanceof を使用します。

class A {}

expect(new A()).toBeInstanceOf(A);
expect(() => {}).toBeInstanceOf(Function);
expect(new A()).toBeInstanceOf(Function); // throws

.toBeNull()

.toBeNull().toBe(null) と同じですが、エラーメッセージがより分かりやすくなります。何かが null であることを確認したい場合は .toBeNull() を使用してください。

function bloop() {
return null;
}

test('bloop returns null', () => {
expect(bloop()).toBeNull();
});

.toBeTruthy()

値の内容を気にせず、ブール値コンテキストで true と評価されることを確認したい場合に .toBeTruthy を使用します。たとえば、次のようなアプリケーションコードがあるとします:

drinkSomeLaCroix();
if (thirstInfo()) {
drinkMoreLaCroix();
}

thirstInfotrue を返すか複雑なオブジェクトを返すかは気にしないかもしれませんが、コードは動作します。したがって、La Croix を飲んだ後に thirstInfo が真値になることをテストしたい場合、次のように記述できます:

test('drinking La Croix leads to having thirst info', () => {
drinkSomeLaCroix();
expect(thirstInfo()).toBeTruthy();
});

JavaScript には 6 つの偽値があります: false, 0, '', null, undefined, NaN です。これら以外の値はすべて真値です。

.toBeUndefined()

変数が undefined であることを確認するには .toBeUndefined を使用します。たとえば、bestDrinkForFlavor(flavor) 関数が 'octopus' フレーバーに対して undefined を返すことをテストする場合(良いオクトパスフレーバーの飲み物がないため):

test('the best drink for octopus flavor is undefined', () => {
expect(bestDrinkForFlavor('octopus')).toBeUndefined();
});

expect(bestDrinkForFlavor('octopus')).toBe(undefined) と記述することもできますが、コード内で直接 undefined を参照することは避けるのがベストプラクティスです。

.toBeNaN()

値が NaN であることを確認する場合に .toBeNaN を使用します。

test('passes when value is NaN', () => {
expect(NaN).toBeNaN();
expect(1).not.toBeNaN();
});

.toContain(item)

配列内に特定のアイテムが含まれていることを確認するには .toContain を使用します。配列アイテムのテストには ===(厳密な等価性チェック)が使用されます。.toContain はある文字列が別の文字列の部分文字列かどうかの確認にも使用できます。

たとえば、getAllFlavors() がフレーバーの配列を返し、その中に lime が含まれていることを確認したい場合:

test('the flavor list contains lime', () => {
expect(getAllFlavors()).toContain('lime');
});

このマッチャーは文字列、セット、ノードリスト、HTML コレクションなどの他の反復可能オブジェクトもサポートしています。

.toContainEqual(item)

特定の構造と値を持つアイテムが配列に含まれていることを確認したい場合、.toContainEqualを使用します。このマッチャーはオブジェクトの同一性をチェックするのではなく、配列内のアイテムをテストする際にすべてのフィールドを再帰的に等価チェックします。

describe('my beverage', () => {
test('is delicious and not sour', () => {
const myBeverage = {delicious: true, sour: false};
expect(myBeverages()).toContainEqual(myBeverage);
});
});

.toEqual(value)

オブジェクトインスタンスのプロパティを再帰的に比較する("ディープ" イコールとも呼ばれる)には.toEqualを使用します。プリミティブ値の比較にはObject.isを呼び出し、これは===厳密等価演算子よりもテストに適しています。

例えば、以下のテストスイートでは.toEqual.toBeの挙動が異なるため、すべてのテストがパスします:

const can1 = {
flavor: 'grapefruit',
ounces: 12,
};
const can2 = {
flavor: 'grapefruit',
ounces: 12,
};

describe('the La Croix cans on my desk', () => {
test('have all the same properties', () => {
expect(can1).toEqual(can2);
});
test('are not the exact same can', () => {
expect(can1).not.toBe(can2);
});
});
ヒント

toEqual は、undefinedプロパティを持つオブジェクトのキー、undefined配列アイテム、配列のスパース性、またはオブジェクトの型の不一致を無視します。これらの点を考慮する場合は、代わりに.toStrictEqualを使用してください。

情報

.toEqualは2つのエラーに対してディープイコールチェックを行いません。Errorのmessageプロパティのみが等価性の対象となります。エラーのテストには.toThrowマッチャーの使用を推奨します。

プロパティの差異がテスト失敗の理由を理解するのに役立たない場合(特にレポートが大きい場合)、比較をexpect関数内に移動できます。例えば、Bufferクラスのequalsメソッドを使用してバッファの内容一致を確認する場合:

  • expect(received).toEqual(expected)expect(received.equals(expected)).toBe(true) に書き換え

  • expect(received).not.toEqual(expected)expect(received.equals(expected)).toBe(false) に書き換え

.toMatch(regexp | string)

文字列が正規表現にマッチすることを確認するには.toMatchを使用します。

例えば、essayOnTheBestFlavor()が正確に何を返すかわからなくても、非常に長い文字列で部分文字列grapefruitが含まれているはずだとします。次のようにテストできます:

describe('an essay on the best flavor', () => {
test('mentions grapefruit', () => {
expect(essayOnTheBestFlavor()).toMatch(/grapefruit/);
expect(essayOnTheBestFlavor()).toMatch(new RegExp('grapefruit'));
});
});

このマッチャーは文字列も受け付け、マッチングを試みます:

describe('grapefruits are healthy', () => {
test('grapefruits are a fruit', () => {
expect('grapefruits').toMatch('fruit');
});
});

.toMatchObject(object)

JavaScriptオブジェクトが別のオブジェクトのプロパティのサブセットと一致することを確認するには.toMatchObjectを使用します。期待されるオブジェクトに存在しないプロパティも受信オブジェクトに含まれている場合にマッチします。

オブジェクトの配列も渡せます。この場合、受信配列の各オブジェクトが期待配列の対応するオブジェクトに(上記のtoMatchObjectの意味で)一致する場合にのみtrueを返します。受信配列の余分な要素を許容するarrayContainingとは異なり、要素数が一致することを確認するのに有用です。

プロパティを値またはマッチャーに対してマッチさせられます。

const houseForSale = {
bath: true,
bedrooms: 4,
kitchen: {
amenities: ['oven', 'stove', 'washer'],
area: 20,
wallColor: 'white',
},
};
const desiredHouse = {
bath: true,
kitchen: {
amenities: ['oven', 'stove', 'washer'],
wallColor: expect.stringMatching(/white|yellow/),
},
};

test('the house has my desired features', () => {
expect(houseForSale).toMatchObject(desiredHouse);
});
describe('toMatchObject applied to arrays', () => {
test('the number of elements must match exactly', () => {
expect([{foo: 'bar'}, {baz: 1}]).toMatchObject([{foo: 'bar'}, {baz: 1}]);
});

test('.toMatchObject is called for each elements, so extra object properties are okay', () => {
expect([{foo: 'bar'}, {baz: 1, extra: 'quux'}]).toMatchObject([
{foo: 'bar'},
{baz: 1},
]);
});
});

.toMatchSnapshot(propertyMatchers?, hint?)

値が直近のスナップショットと一致することを保証します。詳細はスナップショットテストガイドを参照してください。

受信値がオブジェクトインスタンスの場合、オプションの propertyMatchers オブジェクト引数を指定できます。この引数は、期待されるプロパティのサブセットに対して非対称マッチャーを値として持ちます。これは、一部のプロパティには柔軟な基準で toMatchObject のように動作し、残りのプロパティには厳密な基準でスナップショットテストを行うようなものです。

オプションの hint 文字列引数を指定して、テスト名に追加することができます。Jest はスナップショット名の末尾に常に数字を追加しますが、短い説明文のヒントは、単一it または test ブロック内で複数のスナップショットを区別するのに数字よりも便利かもしれません。Jest は対応する .snap ファイル内でスナップショットを名前でソートします。

.toMatchInlineSnapshot(propertyMatchers?, inlineSnapshot)

値が直近のインラインスナップショットと一致することを保証します。

受信値がオブジェクトインスタンスの場合、オプションの propertyMatchers オブジェクト引数を指定できます。この引数は、期待されるプロパティのサブセットに対して非対称マッチャーを値として持ちます。これは、一部のプロパティには柔軟な基準で toMatchObject のように動作し、残りのプロパティには厳密な基準でスナップショットテストを行うようなものです。

Jestは、テストが初めて実行されるとき、外部の.snapファイルではなくテストファイル内のマッチャーにinlineSnapshot文字列引数を追加します。

インラインスナップショットのセクションで詳細を確認してください。

.toStrictEqual(value)

.toStrictEqual を使用して、オブジェクトが同じ構造と型を持っていることを検証します。

.toEqual との違い:

  • undefined プロパティを持つキーがチェックされます(例: {a: undefined, b: 2}{b: 2} は等しくない)

  • undefined 要素が考慮されます(例: [2][2, undefined] は等しくない)

  • 配列のスパース性がチェックされます(例: [, 1][undefined, 1] は等しくない)

  • オブジェクトの型がチェックされます(例: フィールド ab を持つクラスインスタンスと、フィールド ab を持つリテラルオブジェクトは等しくない)

class LaCroix {
constructor(flavor) {
this.flavor = flavor;
}
}

describe('the La Croix cans on my desk', () => {
test('are not semantically the same', () => {
expect(new LaCroix('lemon')).toEqual({flavor: 'lemon'});
expect(new LaCroix('lemon')).not.toStrictEqual({flavor: 'lemon'});
});
});

.toThrow(error?)

関数が呼び出された時にエラーをスローするかテストするには .toThrow を使用します。例えば、drinkFlavor('octopus') がタコの味の不快さでエラーをスローすることをテストする場合:

test('throws on octopus', () => {
expect(() => {
drinkFlavor('octopus');
}).toThrow();
});
ヒント

コードを関数内でラップする必要があります。そうしないとエラーが捕捉されず、アサーションが失敗します。

特定のエラーがスローされることをテストするにはオプション引数を指定できます:

  • 正規表現: エラーメッセージがパターンに一致する

  • 文字列: エラーメッセージが部分文字列を含む

  • エラーオブジェクト: エラーメッセージがオブジェクトのmessageプロパティと等しい

  • エラークラス: エラーオブジェクトがクラスのインスタンスである

例えば、drinkFlavor が次のように実装されている場合:

function drinkFlavor(flavor) {
if (flavor === 'octopus') {
throw new DisgustingFlavorError('yuck, octopus flavor');
}
// Do some other stuff
}

このエラーがスローされることをテストする方法はいくつかあります:

test('throws on octopus', () => {
function drinkOctopus() {
drinkFlavor('octopus');
}

// Test that the error message says "yuck" somewhere: these are equivalent
expect(drinkOctopus).toThrow(/yuck/);
expect(drinkOctopus).toThrow('yuck');

// Test the exact error message
expect(drinkOctopus).toThrow(/^yuck, octopus flavor$/);
expect(drinkOctopus).toThrow(new Error('yuck, octopus flavor'));

// Test that we get a DisgustingFlavorError
expect(drinkOctopus).toThrow(DisgustingFlavorError);
});

.toThrowErrorMatchingSnapshot(hint?)

.toThrowErrorMatchingSnapshot を使用して、関数が呼び出されたときに直近のスナップショットに一致するエラーをスローすることをテストします

オプションの hint 文字列引数を指定して、テスト名に追加することができます。Jest はスナップショット名の末尾に常に数字を追加しますが、短い説明文のヒントは、単一it または test ブロック内で複数のスナップショットを区別するのに数字よりも便利かもしれません。Jest は対応する .snap ファイル内でスナップショットを名前でソートします。

例えば、drinkFlavor 関数があり、フレーバーが 'octopus' のときにエラーをスローし、次のようにコーディングされているとします。

function drinkFlavor(flavor) {
if (flavor === 'octopus') {
throw new DisgustingFlavorError('yuck, octopus flavor');
}
// Do some other stuff
}

この関数のテストは次のようになります:

test('throws on octopus', () => {
function drinkOctopus() {
drinkFlavor('octopus');
}

expect(drinkOctopus).toThrowErrorMatchingSnapshot();
});

この関数のテストは次のようになります:

exports[`drinking flavors throws on octopus 1`] = `"yuck, octopus flavor"`;

スナップショットテストの詳細については、Reactツリースナップショットテストをご覧ください。

.toThrowErrorMatchingInlineSnapshot(inlineSnapshot) (インラインスナップショットに一致するエラーを投げる)

.toThrowErrorMatchingInlineSnapshot を使用して、関数が呼び出されたときに直近のスナップショットに一致するエラーをスローすることをテストします。

Jestは、テストが初めて実行されるとき、外部の.snapファイルではなくテストファイル内のマッチャーにinlineSnapshot文字列引数を追加します。

インラインスナップショットのセクションで詳細を確認してください。

非対称マッチャー

expect.anything()

expect.anything()nullundefined以外のあらゆる値にマッチします。リテラル値の代わりにtoEqualtoHaveBeenCalledWith内で使用できます。例えば、モック関数が非nullの引数で呼び出されたことを確認したい場合:

test('map calls its argument with a non-null argument', () => {
const mock = jest.fn();
[1].map(x => mock(x));
expect(mock).toHaveBeenCalledWith(expect.anything());
});

expect.any(constructor)

expect.any(constructor)は指定されたコンストラクタで作成されたオブジェクト、または渡された型のプリミティブ値にマッチします。リテラル値の代わりにtoEqualtoHaveBeenCalledWith内で使用できます。例えば、モック関数が数値で呼び出されたことを確認したい場合:

class Cat {}
function getCat(fn) {
return fn(new Cat());
}

test('randocall calls its callback with a class instance', () => {
const mock = jest.fn();
getCat(mock);
expect(mock).toHaveBeenCalledWith(expect.any(Cat));
});

function randocall(fn) {
return fn(Math.floor(Math.random() * 6 + 1));
}

test('randocall calls its callback with a number', () => {
const mock = jest.fn();
randocall(mock);
expect(mock).toHaveBeenCalledWith(expect.any(Number));
});

expect.arrayContaining(array)

expect.arrayContaining(array)は期待値配列の全要素を含む受信配列にマッチします。つまり期待値配列は受信配列のサブセットです。したがって受信配列が期待値配列にない追加要素を含む場合もマッチします。

以下の場所でリテラル値の代わりに使用できます:

  • toEqualtoHaveBeenCalledWith

  • objectContainingtoMatchObject でのプロパティのマッチング

describe('arrayContaining', () => {
const expected = ['Alice', 'Bob'];
it('matches even if received contains additional elements', () => {
expect(['Alice', 'Bob', 'Eve']).toEqual(expect.arrayContaining(expected));
});
it('does not match if received does not contain expected elements', () => {
expect(['Bob', 'Eve']).not.toEqual(expect.arrayContaining(expected));
});
});
describe('Beware of a misunderstanding! A sequence of dice rolls', () => {
const expected = [1, 2, 3, 4, 5, 6];
it('matches even with an unexpected number 7', () => {
expect([4, 1, 6, 7, 3, 5, 2, 5, 4, 6]).toEqual(
expect.arrayContaining(expected),
);
});
it('does not match without an expected number 2', () => {
expect([4, 1, 6, 7, 3, 5, 7, 5, 4, 6]).not.toEqual(
expect.arrayContaining(expected),
);
});
});

expect.not.arrayContaining(array)

expect.not.arrayContaining(array)は期待値配列の全要素を含まない受信配列にマッチします。つまり期待値配列が受信配列のサブセットではない場合です。

expect.arrayContainingの逆の挙動を示します。

describe('not.arrayContaining', () => {
const expected = ['Samantha'];

it('matches if the actual array does not contain the expected elements', () => {
expect(['Alice', 'Bob', 'Eve']).toEqual(
expect.not.arrayContaining(expected),
);
});
});

expect.arrayOf(value)

expect.arrayOf(value)は全要素が指定された値にマッチする受信配列にマッチします。配列の全項目が特定の条件や型を満たすことを検証するのに便利です。

例:

test('all elements in array are strings', () => {
expect(['apple', 'banana', 'cherry']).toEqual(
expect.arrayOf(expect.any(String)),
);
});

このマッチャーは複雑な構造を含む配列の検証に特に有用です:

test('array of objects with specific properties', () => {
expect([
{id: 1, name: 'Alice'},
{id: 2, name: 'Bob'},
]).toEqual(
expect.arrayOf(
expect.objectContaining({
id: expect.any(Number),
name: expect.any(String),
}),
),
);
});

expect.not.arrayOf(value)

expect.not.arrayOf(value)は全要素が指定されたマッチャーにマッチしない受信配列にマッチします。

例:

test('not all elements in array are strings', () => {
expect(['apple', 123, 'cherry']).toEqual(
expect.not.arrayOf(expect.any(String)),
);
});

expect.closeTo(number, numDigits?)

expect.closeTo(number, numDigits?)はオブジェクトプロパティや配列要素内の浮動小数点数を比較する際に有用です。数値自体を比較する場合は.toBeCloseToを使用してください。

オプションのnumDigits引数は小数部のチェック桁数を制限します。デフォルト値2の場合、検証条件はMath.abs(expected - received) < 0.005 (that is, 10 ** -2 / 2)となります。

例えば5桁の精度で検証するテスト:

test('compare float in object properties', () => {
expect({
title: '0.1 + 0.2',
sum: 0.1 + 0.2,
}).toEqual({
title: '0.1 + 0.2',
sum: expect.closeTo(0.3, 5),
});
});

expect.objectContaining(object)

expect.objectContaining(object)は期待プロパティを再帰的に含む受信オブジェクトにマッチします。つまり期待オブジェクトは受信オブジェクトのサブセットです。したがって受信オブジェクトが期待オブジェクトに存在するプロパティを全て含む場合にマッチします。

期待オブジェクト内のリテラル値の代わりに、マッチャーやexpect.anything()などを使用できます。

例えばonPress関数がEventオブジェクトで呼び出され、event.xevent.yプロパティの存在だけを検証したい場合:

test('onPress gets called with the right thing', () => {
const onPress = jest.fn();
simulatePresses(onPress);
expect(onPress).toHaveBeenCalledWith(
expect.objectContaining({
x: expect.any(Number),
y: expect.any(Number),
}),
);
});

expect.not.objectContaining(object)

expect.not.objectContaining(object)は期待プロパティを再帰的に含まない受信オブジェクトにマッチします。つまり期待オブジェクトが受信オブジェクトのサブセットではない場合です。したがって受信オブジェクトが期待オブジェクトにない追加プロパティを含む場合にマッチします。

これは expect.objectContaining の逆の挙動です。

describe('not.objectContaining', () => {
const expected = {foo: 'bar'};

it('matches if the actual object does not contain expected key: value pairs', () => {
expect({bar: 'baz'}).toEqual(expect.not.objectContaining(expected));
});
});

expect.stringContaining(string)

expect.stringContaining(string) は、期待する文字列を完全に含む文字列を受け取った場合にマッチします。

expect.not.stringContaining(string)

expect.not.stringContaining(string) は、文字列ではない値、または期待する文字列を含まない文字列を受け取った場合にマッチします。

これは expect.stringContaining の逆の挙動です。

describe('not.stringContaining', () => {
const expected = 'Hello world!';

it('matches if the received value does not contain the expected substring', () => {
expect('How are you?').toEqual(expect.not.stringContaining(expected));
});
});

expect.stringMatching(string | regexp)

expect.stringMatching(string | regexp) は、期待する文字列または正規表現にマッチする文字列を受け取った場合にマッチします。

以下の場所でリテラル値の代わりに使用できます:

  • toEqualtoHaveBeenCalledWith

  • arrayContaining での配列要素のマッチング

  • objectContainingtoMatchObject でのプロパティのマッチング

この例では、expect.arrayContaining 内に expect.stringMatching をネストする方法も示しています。

describe('stringMatching in arrayContaining', () => {
const expected = [
expect.stringMatching(/^Alic/),
expect.stringMatching(/^[BR]ob/),
];
it('matches even if received contains additional elements', () => {
expect(['Alicia', 'Roberto', 'Evelina']).toEqual(
expect.arrayContaining(expected),
);
});
it('does not match if received does not contain expected elements', () => {
expect(['Roberto', 'Evelina']).not.toEqual(
expect.arrayContaining(expected),
);
});
});

expect.not.stringMatching(string | regexp)

expect.not.stringMatching(string | regexp) は、文字列ではない値、または期待する文字列/正規表現にマッチしない文字列を受け取った場合にマッチします。

これは expect.stringMatching の逆の挙動です。

describe('not.stringMatching', () => {
const expected = /Hello world!/;

it('matches if the received value does not match the expected regex', () => {
expect('How are you?').toEqual(expect.not.stringMatching(expected));
});
});

アサーション数の検証

expect.assertions(number)

expect.assertions(number) は、テスト中に特定の数のアサーションが呼び出されることを検証します。非同期コードのテスト時に、コールバック内のアサーションが実際に呼び出されたことを確認するのに特に有用です。

例えば、doAsync 関数が2つのコールバック callback1callback2 を受け取り、未知の順序で非同期に両方を呼び出す場合を考えます:

test('doAsync calls both callbacks', () => {
expect.assertions(2);
function callback1(data) {
expect(data).toBeTruthy();
}
function callback2(data) {
expect(data).toBeTruthy();
}

doAsync(callback1, callback2);
});

expect.assertions(2) の呼び出しにより、両方のコールバックが実際に呼び出されることを保証します。

expect.hasAssertions()

expect.hasAssertions() は、テスト中に少なくとも1つのアサーションが呼び出されることを検証します。非同期コードのテスト時に、コールバック内のアサーションが実際に呼び出されたことを確認するのに特に有用です。

例えば、状態を扱う複数の関数がある場合を考えます。prepareState は状態オブジェクトと共にコールバックを呼び出し、validateState はその状態オブジェクトで実行され、waitOnState は全ての prepareState コールバックが完了するまで待機するPromiseを返します:

test('prepareState prepares a valid state', () => {
expect.hasAssertions();
prepareState(state => {
expect(validateState(state)).toBeTruthy();
});
return waitOnState();
});

expect.hasAssertions() の呼び出しにより、prepareState コールバックが実際に呼び出されることを保証します。

ユーティリティの拡張

expect.addEqualityTesters(testers)

expect.addEqualityTesters を使用して、2つのオブジェクトが等しいかどうかをテストする独自のメソッドを追加できます。例えば、コード内に体積を表すクラスがあり、異なる単位を使用する2つの体積が等しいかどうかを判断できるとします。Volumeクラスを比較する際に、toEqual(および他の等価性マッチャー)がこのカスタム等価メソッドを使用するようにしたい場合があります。カスタム等価テスターを追加することで、toEqualがVolumeクラスを比較する際にカスタムロジックを検出して適用するように設定できます:

Volume.js
// For simplicity in this example, we'll just support the units 'L' and 'mL'
export class Volume {
constructor(amount, unit) {
this.amount = amount;
this.unit = unit;
}

toString() {
return `[Volume ${this.amount}${this.unit}]`;
}

equals(other) {
if (this.unit === other.unit) {
return this.amount === other.amount;
} else if (this.unit === 'L' && other.unit === 'mL') {
return this.amount * 1000 === other.unit;
} else {
return this.amount === other.unit * 1000;
}
}
}
areVolumesEqual.js
import {expect} from '@jest/globals';
import {Volume} from './Volume.js';

function areVolumesEqual(a, b) {
const isAVolume = a instanceof Volume;
const isBVolume = b instanceof Volume;

if (isAVolume && isBVolume) {
return a.equals(b);
} else if (isAVolume === isBVolume) {
return undefined;
} else {
return false;
}
}

expect.addEqualityTesters([areVolumesEqual]);
__tests__/Volume.test.js
import {expect, test} from '@jest/globals';
import {Volume} from '../Volume.js';
import '../areVolumesEqual.js';

test('are equal with different units', () => {
expect(new Volume(1, 'L')).toEqual(new Volume(1000, 'mL'));
});

カスタム等価性テスターのAPI

カスタムテスターは、2つの引数の等価性を比較した結果(true または false)を返す関数、または与えられたオブジェクトを処理せず他のテスター(組み込みの等価性テスターなど)に委譲したい場合に undefined を返す関数です。

カスタムテスターは3つの引数で呼び出されます:比較する2つのオブジェクトと、カスタムテスターの配列(再帰的なテスト用。下記セクション参照)。

これらのヘルパー関数とプロパティは、カスタムテスター内の this からアクセスできます:

this.equals(a, b, customTesters?)

これは深い等値性を検査する関数で、2つのオブジェクトが(再帰的に)同じ値を持つ場合に true を返します。オプションでカスタム等値テスターのリストを受け取り、深い等値性チェックに適用できます。この関数を使用する場合、テスターが受け取ったカスタムテスターを渡すことで、equals が適用するさらなる等値性チェックでも、テスト作成者が設定したカスタムテスターを利用できます。詳細は再帰的なカスタム等値テスターの例を参照してください。

マッチャー vs テスター

マッチャーは expect で利用可能なメソッドです(例: expect().toEqual())。toEqual はマッチャーです。テスターは、オブジェクトが同じかどうかを判断するために等値性チェックを行うマッチャーが使用するメソッドです。

カスタムマッチャーは、テスト作成者がテストで使用できるカスタムアサーションを提供したい場合に適しています。たとえば、expect.extendセクションの toBeWithinRange の例は、優れたカスタムマッチャーの例です。テスト作成者が2つの数値が正確に等しいことをアサートしたい場合は toBe を使用すべきです。一方、テストにある程度の柔軟性を持たせたい場合は、toBeWithinRange の方が適切なアサーションとなるでしょう。

カスタム等値テスターは、すべての等値比較にカスタムロジックを適用するためにJestマッチャーをグローバルに拡張するのに適しています。テスト作成者は特定のアサーションに対してカスタムテスターを有効にし、他のアサーションでは無効にすることはできません(そのような動作が必要な場合はカスタムマッチャーを使用すべきです)。たとえば、すべてのマッチャーに対して2つの Volume オブジェクトが等しいかどうかをチェックする方法を定義するのは、優れたカスタム等値テスターの例です。

再帰的なカスタム等値テスター

カスタム等値テスターが深い等値性を検査したいプロパティを持つオブジェクトをテストする場合、等値テスターで利用可能な this.equals ヘルパーを使用すべきです。この equals メソッドは、Jestが内部で使用する深い等値性比較と同じメソッドであり、カスタム等値テスターを呼び出すメソッドです。第3引数としてカスタム等値テスターの配列を受け取ります。カスタム等値テスターもまた、第3引数としてカスタムテスターの配列を受け取ります。この引数を equals の第3引数として渡すことで、オブジェクトのより深い部分での等値性チェックでもカスタム等値テスターを活用できます。

たとえば、Author クラスの配列を含む Book クラスがあり、両方のクラスにカスタムテスターがあるとします。Book のカスタムテスターは Author の配列に対して深い等値性チェックを行い、与えられたカスタムテスターを渡すことで、Author のカスタム等値テスターが適用されるようにします:

customEqualityTesters.js
function areAuthorEqual(a, b) {
const isAAuthor = a instanceof Author;
const isBAuthor = b instanceof Author;

if (isAAuthor && isBAuthor) {
// Authors are equal if they have the same name
return a.name === b.name;
} else if (isAAuthor === isBAuthor) {
return undefined;
} else {
return false;
}
}

function areBooksEqual(a, b, customTesters) {
const isABook = a instanceof Book;
const isBBook = b instanceof Book;

if (isABook && isBBook) {
// Books are the same if they have the same name and author array. We need
// to pass customTesters to equals here so the Author custom tester will be
// used when comparing Authors
return (
a.name === b.name && this.equals(a.authors, b.authors, customTesters)
);
} else if (isABook === isBBook) {
return undefined;
} else {
return false;
}
}

expect.addEqualityTesters([areAuthorsEqual, areBooksEqual]);
メモ

テスターコンテキストヘルパー(例: this.equals)にアクセスするためには、等値テスターをアロー関数ではなく通常の関数として定義してください。

expect.addSnapshotSerializer(serializer)

expect.addSnapshotSerializer を呼び出すことで、アプリケーション固有のデータ構造をフォーマットするモジュールを追加できます。

個々のテストファイルでは、追加されたモジュールは snapshotSerializers 設定のモジュールより優先され、それらは組み込みJavaScript型やReact要素のデフォルトスナップショットシリアライザより優先されます。最後に追加されたモジュールが最初にテストされます。

import serializer from 'my-serializer-module';
expect.addSnapshotSerializer(serializer);

// affects expect(value).toMatchSnapshot() assertions in the test file

スナップショットシリアライザを snapshotSerializers 設定に追加する代わりに個々のテストファイルで追加する場合:

  • 依存関係を暗黙的ではなく明示的にします

  • create-react-appからイジェクト(脱出)する原因となる設定制限を回避できます

詳細については Jestの設定 を参照してください。

expect.extend(matchers)

expect.extend を使用して、独自のマッチャーをJestに追加できます。たとえば、数値ユーティリティライブラリをテストしていて、数値が他の数値の特定の範囲内にあることを頻繁にアサートする場合を考えてみましょう。これを toBeWithinRange マッチャーに抽象化できます:

toBeWithinRange.js
import {expect} from '@jest/globals';

function toBeWithinRange(actual, floor, ceiling) {
if (
typeof actual !== 'number' ||
typeof floor !== 'number' ||
typeof ceiling !== 'number'
) {
throw new TypeError('These must be of type number!');
}

const pass = actual >= floor && actual <= ceiling;
if (pass) {
return {
message: () =>
`expected ${this.utils.printReceived(
actual,
)} not to be within range ${this.utils.printExpected(
`${floor} - ${ceiling}`,
)}`,
pass: true,
};
} else {
return {
message: () =>
`expected ${this.utils.printReceived(
actual,
)} to be within range ${this.utils.printExpected(
`${floor} - ${ceiling}`,
)}`,
pass: false,
};
}
}

expect.extend({
toBeWithinRange,
});
__tests__/ranges.test.js
import {expect, test} from '@jest/globals';
import '../toBeWithinRange';

test('is within range', () => expect(100).toBeWithinRange(90, 110));

test('is NOT within range', () => expect(101).not.toBeWithinRange(0, 100));

test('asymmetric ranges', () => {
expect({apples: 6, bananas: 3}).toEqual({
apples: expect.toBeWithinRange(1, 10),
bananas: expect.not.toBeWithinRange(11, 20),
});
});
toBeWithinRange.d.ts
// optionally add a type declaration, e.g. it enables autocompletion in IDEs
declare module 'expect' {
interface AsymmetricMatchers {
toBeWithinRange(floor: number, ceiling: number): void;
}
interface Matchers<R> {
toBeWithinRange(floor: number, ceiling: number): R;
}
}

export {};
ヒント

マッチャーの型宣言は .d.ts ファイルまたはインポートされた .ts モジュール内に配置できます(それぞれ上記のJSとTSの例を参照)。.d.ts ファイルで宣言を保持する場合は、プログラムに含まれていること、および有効なモジュールであること(少なくとも空の export {} があること)を確認してください。

ヒント

テストファイルに toBeWithinRange モジュールをインポートする代わりに、expect.extend の呼び出しを setupFilesAfterEnv スクリプトに移動することで、すべてのテストでマッチャーを有効化できます:

import {expect} from '@jest/globals';
// remember to export `toBeWithinRange` as well
import {toBeWithinRange} from './toBeWithinRange';

expect.extend({
toBeWithinRange,
});

非同期マッチャー

expect.extend は非同期マッチャーもサポートしています。非同期マッチャーは Promise を返すため、返される値を await する必要があります。使用法を説明するために、toBeDivisibleByExternalValue というマッチャーを実装する例を示します。ここで除算する数値は外部ソースから取得されます。

expect.extend({
async toBeDivisibleByExternalValue(received) {
const externalValue = await getExternalValueFromRemoteSource();
const pass = received % externalValue === 0;
if (pass) {
return {
message: () =>
`expected ${received} not to be divisible by ${externalValue}`,
pass: true,
};
} else {
return {
message: () =>
`expected ${received} to be divisible by ${externalValue}`,
pass: false,
};
}
},
});

test('is divisible by external value', async () => {
await expect(100).toBeDivisibleByExternalValue();
await expect(101).not.toBeDivisibleByExternalValue();
});

カスタムマッチャーAPI

マッチャーは2つのキーを持つオブジェクト(またはそのPromise)を返す必要があります。pass はマッチしたかどうかを示し、message は失敗時にエラーメッセージを返す引数なしの関数を提供します。したがって、pass が false の場合、messageexpect(x).yourMatcher() が失敗したときのエラーメッセージを返す必要があります。pass が true の場合、messageexpect(x).not.yourMatcher() が失敗したときのエラーメッセージを返す必要があります。

マッチャーは expect(x) に渡された引数と、その後に .yourMatcher(y, z) に渡された引数で呼び出されます:

expect.extend({
yourMatcher(x, y, z) {
return {
pass: true,
message: () => '',
};
},
});

カスタムマッチャー内の this には以下のヘルパー関数とプロパティが存在します:

this.isNot

否定の .not 修飾子付きでマッチャーが呼び出されたかどうかを示すブール値。これにより明確で正確なマッチャーヒントを表示できます(サンプルコード参照)。

this.promise

明確で正確なマッチャーヒントを表示できる文字列:

  • Promise の .rejects 修飾子付きでマッチャーが呼び出された場合は 'rejects'

  • Promise の .resolves 修飾子付きでマッチャーが呼び出された場合は 'resolves'

  • Promise 修飾子なしで呼び出された場合は ''

this.equals(a, b, customTesters?)

深い等価性をチェックする関数。2つのオブジェクトが(再帰的に)同じ値を持つ場合に true を返します。オプションでディープイコールチェックに適用するカスタム等価テスターのリストを受け取ります(下記の this.customTesters 参照)。

this.expand

マッチャーが expand オプション付きで呼び出されたかどうかを示すブール値。Jest が --expand フラグ付きで呼び出された場合、this.expand を使用して完全な差分とエラーを表示するかどうかを判断できます。

this.utils

this.utils には jest-matcher-utils からエクスポートされた有用なツールが多数含まれています。

最も有用なのは matcherHint, printExpected, printReceived で、エラーメッセージを適切にフォーマットできます。例えば toBe マッチャーの実装を参照してください:

const {diff} = require('jest-diff');
expect.extend({
toBe(received, expected) {
const options = {
comment: 'Object.is equality',
isNot: this.isNot,
promise: this.promise,
};

const pass = Object.is(received, expected);

const message = pass
? () =>
// eslint-disable-next-line prefer-template
this.utils.matcherHint('toBe', undefined, undefined, options) +
'\n\n' +
`Expected: not ${this.utils.printExpected(expected)}\n` +
`Received: ${this.utils.printReceived(received)}`
: () => {
const diffString = diff(expected, received, {
expand: this.expand,
});
return (
// eslint-disable-next-line prefer-template
this.utils.matcherHint('toBe', undefined, undefined, options) +
'\n\n' +
(diffString && diffString.includes('- Expect')
? `Difference:\n\n${diffString}`
: `Expected: ${this.utils.printExpected(expected)}\n` +
`Received: ${this.utils.printReceived(received)}`)
);
};

return {actual: received, message, pass};
},
});

これにより以下のような出力が生成されます:

  expect(received).toBe(expected)

Expected value to be (using Object.is):
"banana"
Received:
"apple"

アサーションが失敗した場合、エラーメッセージはユーザーが問題を迅速に解決できるよう十分な情報を提供する必要があります。カスタムアサーションのユーザーが良い開発者体験を得られるよう、正確な失敗メッセージを作成してください。

this.customTesters

matcher が this.equals を使用して深い等価性チェックを行う場合、ユーザー提供のカスタムテスターを this.equals に渡す必要が生じることがあります。addEqualityTesters API 経由でユーザーが提供したカスタム等価性テスターは、このプロパティで利用可能です。組み込み Jest matcher は深い等価性チェックのために this.customTesters(他の組み込みテスターと共に)を this.equals に渡しており、カスタム matcher でも同じ手法が使えます。

カスタムスナップショット matcher

カスタム matcher 内でスナップショットテストを使用するには、jest-snapshot をインポートし matcher 内で活用します。

以下は文字列を指定長にトリミングして保存するスナップショット matcher .toMatchTrimmedSnapshot(length) の実装例です:

const {toMatchSnapshot} = require('jest-snapshot');

expect.extend({
toMatchTrimmedSnapshot(received, length) {
return toMatchSnapshot.call(
this,
received.slice(0, length),
'toMatchTrimmedSnapshot',
);
},
});

it('stores only 10 characters', () => {
expect('extra long string oh my gerd').toMatchTrimmedSnapshot(10);
});

/*
Stored snapshot will look like:

exports[`stores only 10 characters: toMatchTrimmedSnapshot 1`] = `"extra long"`;
*/

インラインスナップショット用のカスタム matcher も作成可能で、スナップショットはカスタム matcher に正しく追加されます。ただしインラインスナップショットは常に最初の引数(最初の引数がプロパティ matcher の場合は2番目)に追加しようとするため、カスタム matcher で追加引数を受け取ることはできません。

const {toMatchInlineSnapshot} = require('jest-snapshot');

expect.extend({
toMatchTrimmedInlineSnapshot(received, ...rest) {
return toMatchInlineSnapshot.call(this, received.slice(0, 10), ...rest);
},
});

it('stores only 10 characters', () => {
expect('extra long string oh my gerd').toMatchTrimmedInlineSnapshot();
/*
The snapshot will be added inline like
expect('extra long string oh my gerd').toMatchTrimmedInlineSnapshot(
`"extra long"`
);
*/
});

async

カスタムインラインスナップショット matcher が非同期(async-await 使用)の場合、"Multiple inline snapshots for the same call are not supported" エラーが発生する可能性があります。Jest がスナップショットを正しく更新するには、カスタムインラインスナップショット matcher の使用箇所を特定するための追加コンテキストが必要です。

const {toMatchInlineSnapshot} = require('jest-snapshot');

expect.extend({
async toMatchObservationInlineSnapshot(fn, ...rest) {
// The error (and its stacktrace) must be created before any `await`
this.error = new Error();

// The implementation of `observe` doesn't matter.
// It only matters that the custom snapshot matcher is async.
const observation = await observe(async () => {
await fn();
});

return toMatchInlineSnapshot.call(this, recording, ...rest);
},
});

it('observes something', async () => {
await expect(async () => {
return 'async action';
}).toMatchTrimmedInlineSnapshot();
/*
The snapshot will be added inline like
await expect(async () => {
return 'async action';
}).toMatchTrimmedInlineSnapshot(`"async action"`);
*/
});

早期終了

通常 jest はテスト内の全スナップショットの照合を試みます。

事前スナップショットが失敗した場合、テスト継続が意味をなさない場合があります。例えば状態マシンの遷移後スナップショットを作成する際、ある遷移で不正な状態が生成されたらテストを中止できます。

この場合、不一致を全て収集する代わりに最初の不一致でエラーをスローするカスタムスナップショット matcher を実装できます。

const {toMatchInlineSnapshot} = require('jest-snapshot');

expect.extend({
toMatchStateInlineSnapshot(...args) {
this.dontThrow = () => {};

return toMatchInlineSnapshot.call(this, ...args);
},
});

let state = 'initial';

function transition() {
// Typo in the implementation should cause the test to fail
if (state === 'INITIAL') {
state = 'pending';
} else if (state === 'pending') {
state = 'done';
}
}

it('transitions as expected', () => {
expect(state).toMatchStateInlineSnapshot(`"initial"`);

transition();
// Already produces a mismatch. No point in continuing the test.
expect(state).toMatchStateInlineSnapshot(`"loading"`);

transition();
expect(state).toMatchStateInlineSnapshot(`"done"`);
});

シリアライズ可能なプロパティ

SERIALIZABLE_PROPERTIES

シリアライズ可能なプロパティとは、Jest がシリアライズ可能とみなすプロパティのセットです。このセットはプロパティのシリアライズ可否を判定するために使用され、セットに含まれないプロパティはシリアライズ不可とみなされエラーメッセージに表示されません。

オブジェクトを正しく表示させるため、このセットに独自プロパティを追加できます。例えば Volume クラスがあり、amountunit プロパティのみ表示したい場合、それを SERIALIZABLE_PROPERTIES に追加できます:

import {SERIALIZABLE_PROPERTIES} from 'jest-matcher-utils';

class Volume {
constructor(amount, unit) {
this.amount = amount;
this.unit = unit;
}

get label() {
throw new Error('Not implemented');
}
}

Volume.prototype[SERIALIZABLE_PROPERTIES] = ['amount', 'unit'];

expect(new Volume(1, 'L')).toEqual(new Volume(10, 'L'));

これによりエラーメッセージでは label プロパティが無視され、amountunit プロパティのみが表示されます。

expect(received).toEqual(expected) // deep equality

Expected: {"amount": 10, "unit": "L"}
Received: {"amount": 1, "unit": "L"}