Expect
在撰寫測試時,您通常需要檢查值是否符合特定條件。expect
讓您可以存取多個「比對器」,讓您驗證不同的內容。
如需 Jest 社群維護的其他 Jest 比對器,請查看 jest-extended
。
此頁面的 TypeScript 範例僅在您明確匯入 Jest API 時,才會如文件所述般運作
import {expect, jest, test} from '@jest/globals';
有關如何使用 TypeScript 設定 Jest 的詳細資訊,請參閱入門指南。
參考
- Expect
- 修改器
- 比對器
.toBe(value)
.toHaveBeenCalled()
.toHaveBeenCalledTimes(number)
.toHaveBeenCalledWith(arg1, arg2, ...)
.toHaveBeenLastCalledWith(arg1, arg2, ...)
.toHaveBeenNthCalledWith(nthCall, arg1, arg2, ....)
.toHaveReturned()
.toHaveReturnedTimes(number)
.toHaveReturnedWith(value)
.toHaveLastReturnedWith(value)
.toHaveNthReturnedWith(nthCall, value)
.toHaveLength(number)
.toHaveProperty(keyPath, value?)
.toBeCloseTo(number, numDigits?)
.toBeDefined()
.toBeFalsy()
.toBeGreaterThan(number | bigint)
.toBeGreaterThanOrEqual(number | bigint)
.toBeLessThan(number | bigint)
.toBeLessThanOrEqual(number | bigint)
.toBeInstanceOf(Class)
.toBeNull()
.toBeTruthy()
.toBeUndefined()
.toBeNaN()
.toContain(item)
.toContainEqual(item)
.toEqual(value)
.toMatch(regexp | string)
.toMatchObject(object)
.toMatchSnapshot(propertyMatchers?, hint?)
.toMatchInlineSnapshot(propertyMatchers?, inlineSnapshot)
.toStrictEqual(value)
.toThrow(error?)
.toThrowErrorMatchingSnapshot(hint?)
.toThrowErrorMatchingInlineSnapshot(inlineSnapshot)
- 非對稱比對器
expect.anything()
expect.any(constructor)
expect.arrayContaining(array)
expect.not.arrayContaining(array)
expect.closeTo(number, numDigits?)
expect.objectContaining(object)
expect.not.objectContaining(object)
expect.stringContaining(string)
expect.not.stringContaining(string)
expect.stringMatching(string | regexp)
expect.not.stringMatching(string | regexp)
- 斷言計數
- 延伸公用程式
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
來解開已實現承諾的承諾值,以便可以串接任何其他比對器。如果承諾遭到拒絕,則斷言失敗。
例如,這個程式碼測試承諾是否已解決,以及結果值是否為 'lemon'
test('resolves to lemon', () => {
// make sure to add a return statement
return expect(Promise.resolve('lemon')).resolves.toBe('lemon');
});
由於你仍在測試承諾,因此測試仍是異步的。因此,你需要透過傳回未解開的斷言來告訴 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
來解開被拒絕承諾的原因,以便可以串接任何其他比對器。如果承諾已實現,則斷言失敗。
例如,這個程式碼測試承諾是否拒絕原因 'octopus'
test('rejects to octopus', () => {
// make sure to add a return statement
return expect(Promise.reject(new Error('octopus'))).rejects.toThrow(
'octopus',
);
});
由於你仍在測試承諾,因此測試仍是異步的。因此,你需要透過傳回未解開的斷言來告訴 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.1
並不嚴格等於 0.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()
別名:.toBeCalled()
使用 .toHaveBeenCalled
確保模擬函式已被呼叫。
例如,假設你有一個 drinkAll(drink, flavour)
函式,它會取得 drink
函式並將其套用至所有可用的飲料。你可能想要檢查 drink
是否被呼叫以取得 'lemon'
,但不要呼叫 'octopus'
,因為 'octopus'
口味真的很奇怪,而且為什麼會有任何東西是章魚口味的?你可以使用這個測試套件來執行此操作
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)
別名:.toBeCalledTimes(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, ...)
別名:.toBeCalledWith()
使用 .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, ...)
別名:.lastCalledWith(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, ....)
別名:.nthCalledWith(nthCall, arg1, arg2, ...)
如果您有模擬函式,您可以使用 .toHaveBeenNthCalledWith
來測試它第 n 次呼叫時所帶的參數。例如,假設您有一個 drinkEach(drink, Array<flavor>)
函式,它會將 f
套用至許多口味,而且您想要確保在呼叫它時,它處理的第一個口味是 'lemon'
,第二個是 '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()
別名:.toReturn()
如果您有模擬函式,您可以使用 .toHaveReturned
來測試模擬函式是否成功回傳(即未擲回錯誤)至少一次。例如,假設您有一個模擬 drink
會回傳 true
。您可以撰寫
test('drinks returns', () => {
const drink = jest.fn(() => true);
drink();
expect(drink).toHaveReturned();
});
.toHaveReturnedTimes(number)
別名:.toReturnTimes(number)
使用 .toHaveReturnedTimes
來確保模擬函式成功回傳(即未擲回錯誤)特定次數。任何會擲回錯誤的模擬函式呼叫都不會計入函式回傳的次數。
例如,假設您有一個模擬 drink
會回傳 true
。您可以撰寫
test('drink returns twice', () => {
const drink = jest.fn(() => true);
drink();
drink();
expect(drink).toHaveReturnedTimes(2);
});
.toHaveReturnedWith(value)
別名:.toReturnWith(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)
別名:.lastReturnedWith(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)
別名:.nthReturnedWith(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
的屬性。對於檢查物件中深度巢狀的屬性,您可以使用 點號表示法 或包含深度參考的 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)值的算術運算在有限精度的二進位(底數 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()
使用 .toBeDefined
檢查變數是否未定義。例如,如果您要檢查函式 fetchNewFlavorIdea()
是否傳回 某些東西,您可以撰寫
test('there is a new flavor idea', () => {
expect(fetchNewFlavorIdea()).toBeDefined();
});
您可以撰寫 expect(fetchNewFlavorIdea()).not.toBe(undefined)
,但最好避免在程式碼中直接參照 undefined
。
.toBeFalsy()
當您不關心值是什麼,而且您想要確保值在布林文脈中為 false 時,請使用 .toBeFalsy
。例如,假設您有一些應用程式程式碼看起來像
drinkSomeLaCroix();
if (!getErrors()) {
drinkMoreLaCroix();
}
您可能不關心 getErrors
傳回什麼,特別是它可能會傳回 false
、null
或 0
,而您的程式碼仍然會運作。因此,如果您想要測試在喝了一些 La Croix 之後沒有錯誤,您可以撰寫
test('drinking La Croix does not lead to errors', () => {
drinkSomeLaCroix();
expect(getErrors()).toBeFalsy();
});
在 JavaScript 中,有六個錯誤值:false
、0
、''
、null
、undefined
和 NaN
。其他所有值都是真值。
.toBeGreaterThan(number | bigint)
使用 toBeGreaterThan
比較數字或大整數值的 received > expected
。例如,測試 ouncesPerCan()
傳回的值是否大於 10 盎司
test('ounces per can is more than 10', () => {
expect(ouncesPerCan()).toBeGreaterThan(10);
});
.toBeGreaterThanOrEqual(數字 | 大整數)
使用 toBeGreaterThanOrEqual
來比較數字或大整數值 received >= expected
。例如,測試 ouncesPerCan()
傳回的值至少為 12 盎司
test('ounces per can is at least 12', () => {
expect(ouncesPerCan()).toBeGreaterThanOrEqual(12);
});
.toBeLessThan(數字 | 大整數)
使用 toBeLessThan
來比較數字或大整數值 received < expected
。例如,測試 ouncesPerCan()
傳回的值小於 20 盎司
test('ounces per can is less than 20', () => {
expect(ouncesPerCan()).toBeLessThan(20);
});
.toBeLessThanOrEqual(數字 | 大整數)
使用 toBeLessThanOrEqual
來比較數字或大整數值 received <= expected
。例如,測試 ouncesPerCan()
傳回的值最多為 12 盎司
test('ounces per can is at most 12', () => {
expect(ouncesPerCan()).toBeLessThanOrEqual(12);
});
.toBeInstanceOf(類別)
使用 .toBeInstanceOf(類別)
來檢查物件是否為類別的執行個體。此比對器在底層使用 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();
}
您可能不關心 thirstInfo
傳回的值為何,特別是它可能會傳回 true
或複雜物件,而您的程式碼仍會運作。因此,如果您要測試在喝了一些 La Croix 後,thirstInfo
會為真,您可以撰寫
test('drinking La Croix leads to having thirst info', () => {
drinkSomeLaCroix();
expect(thirstInfo()).toBeTruthy();
});
在 JavaScript 中,有六個錯誤值:false
、0
、''
、null
、undefined
和 NaN
。其他所有值都是真值。
.toBeUndefined()
使用 .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(項目)
當您想要檢查陣列中是否有某個項目時,請使用 .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
對於兩個錯誤,不會執行深度相等檢查。只會考慮錯誤的 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)
使用 .toMatchObject
檢查 JavaScript 物件是否與物件的子集合屬性相符。它會比對接收的物件與預期物件中沒有的屬性。
您也可以傳遞物件陣列,這種情況下,此方法僅在接收陣列中的每個物件都與預期陣列中對應的物件相符(如上所述的 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 會在測試執行時將 inlineSnapshot
字串引數新增至測試檔案中的比對器(而非外部 .snap
檔案)。
查看內嵌快照區段以取得更多資訊。
.toStrictEqual(value)
使用 .toStrictEqual
測試物件是否具有相同的結構和類型。
與 .toEqual
的差異
- 會檢查具有
undefined
屬性的鍵,例如{a: undefined, b: 2}
不同於{b: 2}
; - 會考慮
undefined
項目,例如[2]
不同於[2, undefined]
; - 會檢查陣列稀疏性,例如
[, 1]
不同於[undefined, 1]
; - 會檢查物件類型,例如具有欄位
a
和b
的類別實例不同於具有欄位a
和b
的文字物件。
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?)
別名:.toThrowError(error?)
使用 .toThrow
測試函式在呼叫時會擲回錯誤。例如,若要測試 drinkFlavor('octopus')
會擲回錯誤,因為章魚口味太噁心了,無法飲用,我們可以撰寫
test('throws on octopus', () => {
expect(() => {
drinkFlavor('octopus');
}).toThrow();
});
您必須將程式碼包裝在函式中,否則錯誤不會被捕捉,而斷言會失敗。
您可以提供一個選用引數,以測試擲回特定錯誤
- 正規表示式:錯誤訊息符合模式
- 字串:錯誤訊息包含子字串
- 錯誤物件:錯誤訊息等於物件的訊息屬性
- 錯誤類別:錯誤物件是類別的實例
例如,假設 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 會在測試執行時將 inlineSnapshot
字串引數新增至測試檔案中的比對器(而非外部 .snap
檔案)。
查看內嵌快照區段以取得更多資訊。
非對稱比對器
expect.anything()
expect.anything()
比對任何值,但 null
或 undefined
除外。您可以在 toEqual
或 toHaveBeenCalledWith
中使用它,取代實際值。例如,如果您想要檢查模擬函式是否以非 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)
比對任何使用指定建構函式建立的值,或比對傳入類型為基本型別的值。您可以在 toEqual
或 toHaveBeenCalledWith
中使用它,取代實際值。例如,如果您想要檢查模擬函式是否以數字呼叫
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)
比對接收到的陣列,其中包含預期陣列中的所有元素。也就是說,預期陣列是接收陣列的子集。因此,它會比對包含預期陣列中沒有的元素的接收陣列。
您可以在以下情況中使用它,取代實際值
- 在
toEqual
或toHaveBeenCalledWith
中 - 在
objectContaining
或toMatchObject
中比對屬性
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.closeTo(number, numDigits?)
expect.closeTo(number, numDigits?)
在比較物件屬性或陣列項目中的浮點數時很有用。如果你需要比較一個數字,請改用 .toBeCloseTo
。
可選的 numDigits
參數限制小數點後要檢查的數字位數。對於預設值 2
,測試準則為 Math.abs(expected - received) < 0.005 (即 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.x
和 event.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)
如果接收值是符合預期字串或正規表示式的字串,則符合。
您可以在以下情況中使用它,取代實際值
- 在
toEqual
或toHaveBeenCalledWith
中 - 在
arrayContaining
中符合一個元素 - 在
objectContaining
或toMatchObject
中比對屬性
這個範例也顯示你可以如何巢狀多個非對稱比對器,其中 expect.stringMatching
在 expect.arrayContaining
內部。
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(字串 | 正規表示法)
expect.not.stringMatching(字串 | 正規表示法)
如果接收到的值不是字串,或為不符合預期字串或正規表示法的字串,則會相符。
它是 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(數字)
expect.assertions(數字)
驗證在測試期間呼叫了特定數量的斷言。這在測試非同步程式碼時通常很有用,以確保實際呼叫了回呼中的斷言。
例如,假設我們有一個接收兩個回呼 callback1
和 callback2
的函式 doAsync
,它將以未知順序非同步呼叫它們。我們可以使用以下方式測試:
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()
驗證在測試期間至少呼叫了一個斷言。這在測試非同步程式碼時通常很有用,以確保實際呼叫了回呼中的斷言。
例如,假設我們有幾個函式都處理狀態。prepareState
使用狀態物件呼叫回呼,validateState
在該狀態物件上執行,而 waitOnState
傳回一個承諾,等待所有 prepareState
回呼完成。我們可以使用以下方式測試:
test('prepareState prepares a valid state', () => {
expect.hasAssertions();
prepareState(state => {
expect(validateState(state)).toBeTruthy();
});
return waitOnState();
});
expect.hasAssertions()
呼叫可確保實際呼叫了 prepareState
回呼。
延伸公用程式
expect.addEqualityTesters(測試器)
你可以使用 expect.addEqualityTesters
新增自己的方法來測試兩個物件是否相等。例如,假設你的程式碼中有一個類別代表體積,並且可以判斷使用不同單位表示的兩個體積是否相等。你可能希望 toEqual
(和其他相等比對器) 在與 Volume 類別比較時使用此自訂相等方法。你可以新增一個自訂相等測試器,讓 toEqual
在比較 Volume 類別時偵測並套用自訂邏輯
- JavaScript
- TypeScript
// 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;
}
}
}
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]);
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'));
});
// For simplicity in this example, we'll just support the units 'L' and 'mL'
export class Volume {
public amount: number;
public unit: 'L' | 'mL';
constructor(amount: number, unit: 'L' | 'mL') {
this.amount = amount;
this.unit = unit;
}
toString(): string {
return `[Volume ${this.amount}${this.unit}]`;
}
equals(other: Volume): boolean {
if (this.unit === other.unit) {
return this.amount === other.amount;
} else if (this.unit === 'L' && other.unit === 'mL') {
return this.amount * 1000 === other.amount;
} else {
return this.amount === other.amount * 1000;
}
}
}
import {expect} from '@jest/globals';
import {Volume} from './Volume.js';
function areVolumesEqual(a: unknown, b: unknown): boolean | undefined {
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]);
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
自訂測試器是傳回比較兩個給定參數相等結果 (true
或 false
) 的函式,或是在測試器不處理給定物件並想委派相等給其他測試器 (例如內建相等測試器) 時傳回 undefined
。
自訂測試器會以 3 個參數呼叫:兩個要比較的物件和自訂測試器陣列 (用於遞迴測試器,請參閱下列區段)。
這些輔助函式和屬性可以在自訂測試器的 this
內找到
this.equals(a, b, customTesters?)
這是一個深度相等函式,如果兩個物件有相同的數值 (遞迴),它會傳回 true
。它可以選擇採用自訂相等測試器清單,套用至深度相等檢查。如果您使用這個函式,請傳遞測試器給定的自訂測試器,以便 equals
套用的進一步相等檢查也能使用測試作者可能設定的自訂測試器。請參閱 遞迴自訂相等測試器 區段中的範例,以取得更多詳細資料。
比對器與測試器
比對器是 expect
上可用的方法,例如 expect().toEqual()
。toEqual
是比對器。測試器是比對器使用的方法,用於執行相等檢查,以判斷物件是否相同。
當您想提供測試作者可以在測試中使用的自訂斷言時,自訂比對器很好用。例如,expect.extend
區段中的 toBeWithinRange
範例就是自訂比對器的良好範例。有時,測試作者可能想斷言兩個數字完全相等,並應使用 toBe
。然而,在其他時候,測試作者可能想在測試中允許一些彈性,而 toBeWithinRange
可能是一個更適當的斷言。
自訂相等測試器很適合用於擴充所有相等比較中自訂相等邏輯的 Jest 比對器。測試作者無法為特定斷言開啟自訂測試器,並為其他斷言關閉它們 (如果需要這種行為,應改用自訂比對器)。例如,定義如何檢查兩個 Volume
物件對所有比對器相等,將會是一個良好的自訂相等測試器。
遞迴自訂相等測試器
如果您的自訂相等性測試器正在測試您想要進行深度相等性的物件屬性,您應該使用相等性測試器提供的 this.equals
輔助函式。這個 equals
方法與 Jest 內部用於所有深度相等性比較的方法相同。這個方法會呼叫您的自訂相等性測試器。它接受一個自訂相等性測試器陣列作為第三個參數。自訂相等性測試器也會收到一個自訂測試器陣列作為它們的第三個參數。將這個參數傳遞到 equals
的第三個參數,以便進一步深入物件的相等性檢查也能利用自訂相等性測試器。
例如,假設您有一個包含 Author
類別陣列的 Book
類別,而且這兩個類別都有自訂測試器。Book
自訂測試器會想要對 Author
陣列進行深度相等性檢查,並傳入給它的自訂測試器,以便套用 Author
自訂相等性測試器
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
比對器
- JavaScript
- TypeScript
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,
});
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),
});
});
// 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 {};
import {expect} from '@jest/globals';
import type {MatcherFunction} from 'expect';
const toBeWithinRange: MatcherFunction<[floor: unknown, ceiling: unknown]> =
// `floor` and `ceiling` get types from the line above
// it is recommended to type them as `unknown` and to validate the values
function (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: () =>
// `this` context will have correct typings
`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,
});
declare module 'expect' {
interface AsymmetricMatchers {
toBeWithinRange(floor: number, ceiling: number): void;
}
interface Matchers<R> {
toBeWithinRange(floor: number, ceiling: number): R;
}
}
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),
});
});
比對器的類型宣告可以放在 .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,因此您需要等待傳回的值。我們將使用範例比對器來說明其用法。我們將實作一個稱為 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
比對器應傳回包含兩個金鑰的物件(或物件的 Promise)。pass
表示是否有比對,而 message
提供不帶引數的函式,用於在失敗時傳回錯誤訊息。因此,當 pass
為 false 時,message
應傳回 expect(x).yourMatcher()
失敗時的錯誤訊息。而當 pass
為 true 時,message
應傳回 expect(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?)
這是一個深度相等函式,如果兩個物件具有相同的數值 (遞迴),它會傳回 true
。它可以選擇採用自訂相等測試器清單,套用至深度相等檢查 (請參閱下方的 this.customTesters
)。
this.expand
一個布林值,讓您知道這個比對器是用 expand
選項呼叫的。當 Jest 用 --expand
旗標呼叫時,this.expand
可用於判斷 Jest 是否預期顯示完整的差異和錯誤。
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
如果您的比對器使用 this.equals
進行深度相等性檢查,您可能希望將使用者提供的自訂測試程式傳遞給 this.equals
。使用者使用 addEqualityTesters
API 提供的自訂相等性測試程式會顯示在此屬性上。內建的 Jest 比對器會將 this.customTesters
(以及其他內建測試程式)傳遞給 this.equals
以進行深度相等性檢查,而您的自訂比對器可能也想執行相同的動作。
自訂快照比對器
若要在自訂比對器中使用快照測試,您可以匯入 jest-snapshot
並在比對器中使用它。
以下是一個快照比對器,它會將字串修剪為指定長度以儲存,.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"`;
*/
也可以為內嵌快照建立自訂比對器,快照會正確地加入自訂比對器。但是,內嵌快照會一直嘗試附加至第一個引數,或在第一個引數是屬性比對器時附加至第二個引數,因此無法在自訂比對器中接受自訂引數。
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
-await
,您可能會遇到「不支援同一呼叫的多個內嵌快照」之類的錯誤。Jest 需要額外的內容資訊才能找出自訂內嵌快照比對器用於正確更新快照的位置。
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
會嘗試比對測試中預期的每個快照。
有時,如果先前的快照失敗,繼續測試可能沒有意義。例如,當您在各種轉換後對狀態機進行快照時,您可以在一個轉換產生錯誤狀態後中止測試。
在這種情況下,您可以實作一個自訂快照比對器,它會在第一次不匹配時擲出例外,而不是收集每個不匹配項。
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"`);
});