id | title |
---|---|
expect |
Expect |
When you’re writing tests, you often need to check that values meet certain conditions. expect
gives you access to a number of «matchers» that let you validate different things.
For additional Jest matchers maintained by the Jest Community check out jest-extended
.
import TypeScriptExamplesNote from ‘./_TypeScriptExamplesNote.md’;
Reference
import TOCInline from ‘@theme/TOCInline’;
Expect
expect(value)
The expect
function is used every time you want to test a value. You will rarely call expect
by itself. Instead, you will use expect
along with a «matcher» function to assert something about a value.
It’s easier to understand this with an example. Let’s say you have a method bestLaCroixFlavor()
which is supposed to return the string 'grapefruit'
. Here’s how you would test that:
test('the best flavor is grapefruit', () => { expect(bestLaCroixFlavor()).toBe('grapefruit'); });
In this case, toBe
is the matcher function. There are a lot of different matcher functions, documented below, to help you test different things.
The argument to expect
should be the value that your code produces, and any argument to the matcher should be the correct value. If you mix them up, your tests will still work, but the error messages on failing tests will look strange.
Modifiers
.not
If you know how to test something, .not
lets you test its opposite. For example, this code tests that the best La Croix flavor is not coconut:
test('the best flavor is not coconut', () => { expect(bestLaCroixFlavor()).not.toBe('coconut'); });
.resolves
Use resolves
to unwrap the value of a fulfilled promise so any other matcher can be chained. If the promise is rejected the assertion fails.
For example, this code tests that the promise resolves and that the resulting value is 'lemon'
:
test('resolves to lemon', () => { // make sure to add a return statement return expect(Promise.resolve('lemon')).resolves.toBe('lemon'); });
:::note
Since you are still testing promises, the test is still asynchronous. Hence, you will need to tell Jest to wait by returning the unwrapped assertion.
Alternatively, you can use async/await
in combination with .resolves
:
test('resolves to lemon', async () => { await expect(Promise.resolve('lemon')).resolves.toBe('lemon'); await expect(Promise.resolve('lemon')).resolves.not.toBe('octopus'); });
:::
.rejects
Use .rejects
to unwrap the reason of a rejected promise so any other matcher can be chained. If the promise is fulfilled the assertion fails.
For example, this code tests that the promise rejects with reason 'octopus'
:
test('rejects to octopus', () => { // make sure to add a return statement return expect(Promise.reject(new Error('octopus'))).rejects.toThrow( 'octopus', ); });
:::note
Since you are still testing promises, the test is still asynchronous. Hence, you will need to tell Jest to wait by returning the unwrapped assertion.
Alternatively, you can use async/await
in combination with .rejects
.
test('rejects to octopus', async () => { await expect(Promise.reject(new Error('octopus'))).rejects.toThrow('octopus'); });
:::
Matchers
.toBe(value)
Use .toBe
to compare primitive values or to check referential identity of object instances. It calls Object.is
to compare values, which is even better for testing than ===
strict equality operator.
For example, this code will validate some properties of the can
object:
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'); }); });
Don’t use .toBe
with floating-point numbers. For example, due to rounding, in JavaScript 0.2 + 0.1
is not strictly equal to 0.3
. If you have floating point numbers, try .toBeCloseTo
instead.
Although the .toBe
matcher checks referential identity, it reports a deep comparison of values if the assertion fails. If differences between properties do not help you to understand why a test fails, especially if the report is large, then you might move the comparison into the expect
function. For example, to assert whether or not elements are the same instance:
- rewrite
expect(received).toBe(expected)
asexpect(Object.is(received, expected)).toBe(true)
- rewrite
expect(received).not.toBe(expected)
asexpect(Object.is(received, expected)).toBe(false)
.toHaveBeenCalled()
Also under the alias: .toBeCalled()
Use .toHaveBeenCalledWith
to ensure that a mock function was called with specific arguments. The arguments are checked with the same algorithm that .toEqual
uses.
For example, let’s say you have a drinkAll(drink, flavour)
function that takes a drink
function and applies it to all available beverages. You might want to check that drink
gets called for 'lemon'
, but not for 'octopus'
, because 'octopus'
flavour is really weird and why would anything be octopus-flavoured? You can do that with this test suite:
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)
Also under the alias: .toBeCalledTimes(number)
Use .toHaveBeenCalledTimes
to ensure that a mock function got called exact number of times.
For example, let’s say you have a drinkEach(drink, Array<flavor>)
function that takes a drink
function and applies it to array of passed beverages. You might want to check that drink function was called exact number of times. You can do that with this test suite:
test('drinkEach drinks each drink', () => { const drink = jest.fn(); drinkEach(drink, ['lemon', 'octopus']); expect(drink).toHaveBeenCalledTimes(2); });
.toHaveBeenCalledWith(arg1, arg2, ...)
Also under the alias: .toBeCalledWith()
Use .toHaveBeenCalledWith
to ensure that a mock function was called with specific arguments. The arguments are checked with the same algorithm that .toEqual
uses.
For example, let’s say that you can register a beverage with a register
function, and applyToAll(f)
should apply the function f
to all registered beverages. To make sure this works, you could write:
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, ...)
Also under the alias: .lastCalledWith(arg1, arg2, ...)
If you have a mock function, you can use .toHaveBeenLastCalledWith
to test what arguments it was last called with. For example, let’s say you have a applyToAllFlavors(f)
function that applies f
to a bunch of flavors, and you want to ensure that when you call it, the last flavor it operates on is 'mango'
. You can write:
test('applying to all flavors does mango last', () => { const drink = jest.fn(); applyToAllFlavors(drink); expect(drink).toHaveBeenLastCalledWith('mango'); });
.toHaveBeenNthCalledWith(nthCall, arg1, arg2, ....)
Also under the alias: .nthCalledWith(nthCall, arg1, arg2, ...)
If you have a mock function, you can use .toHaveBeenNthCalledWith
to test what arguments it was nth called with. For example, let’s say you have a drinkEach(drink, Array<flavor>)
function that applies f
to a bunch of flavors, and you want to ensure that when you call it, the first flavor it operates on is 'lemon'
and the second one is 'octopus'
. You can write:
test('drinkEach drinks each drink', () => { const drink = jest.fn(); drinkEach(drink, ['lemon', 'octopus']); expect(drink).toHaveBeenNthCalledWith(1, 'lemon'); expect(drink).toHaveBeenNthCalledWith(2, 'octopus'); });
:::note
The nth argument must be positive integer starting from 1.
:::
.toHaveReturned()
Also under the alias: .toReturn()
If you have a mock function, you can use .toHaveReturned
to test that the mock function successfully returned (i.e., did not throw an error) at least one time. For example, let’s say you have a mock drink
that returns true
. You can write:
test('drinks returns', () => { const drink = jest.fn(() => true); drink(); expect(drink).toHaveReturned(); });
.toHaveReturnedTimes(number)
Also under the alias: .toReturnTimes(number)
Use .toHaveReturnedTimes
to ensure that a mock function returned successfully (i.e., did not throw an error) an exact number of times. Any calls to the mock function that throw an error are not counted toward the number of times the function returned.
For example, let’s say you have a mock drink
that returns true
. You can write:
test('drink returns twice', () => { const drink = jest.fn(() => true); drink(); drink(); expect(drink).toHaveReturnedTimes(2); });
.toHaveReturnedWith(value)
Also under the alias: .toReturnWith(value)
Use .toHaveReturnedWith
to ensure that a mock function returned a specific value.
For example, let’s say you have a mock drink
that returns the name of the beverage that was consumed. You can write:
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)
Also under the alias: .lastReturnedWith(value)
Use .toHaveLastReturnedWith
to test the specific value that a mock function last returned. If the last call to the mock function threw an error, then this matcher will fail no matter what value you provided as the expected return value.
For example, let’s say you have a mock drink
that returns the name of the beverage that was consumed. You can write:
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)
Also under the alias: .nthReturnedWith(nthCall, value)
Use .toHaveNthReturnedWith
to test the specific value that a mock function returned for the nth call. If the nth call to the mock function threw an error, then this matcher will fail no matter what value you provided as the expected return value.
For example, let’s say you have a mock drink
that returns the name of the beverage that was consumed. You can write:
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)'); });
:::note
The nth argument must be positive integer starting from 1.
:::
.toHaveLength(number)
Use .toHaveLength
to check that an object has a .length
property and it is set to a certain numeric value.
This is especially useful for checking arrays or strings size.
expect([1, 2, 3]).toHaveLength(3); expect('abc').toHaveLength(3); expect('').not.toHaveLength(5);
.toHaveProperty(keyPath, value?)
Use .toHaveProperty
to check if property at provided reference keyPath
exists for an object. For checking deeply nested properties in an object you may use dot notation or an array containing the keyPath for deep references.
You can provide an optional value
argument to compare the received property value (recursively for all properties of object instances, also known as deep equality, like the toEqual
matcher).
The following example contains a houseForSale
object with nested properties. We are using toHaveProperty
to check for the existence and values of various properties in the object.
// 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?)
Use toBeCloseTo
to compare floating point numbers for approximate equality.
The optional numDigits
argument limits the number of digits to check after the decimal point. For the default value 2
, the test criterion is Math.abs(expected - received) < 0.005
(that is, 10 ** -2 / 2
).
Intuitive equality comparisons often fail, because arithmetic on decimal (base 10) values often have rounding errors in limited precision binary (base 2) representation. For example, this test fails:
test('adding works sanely with decimals', () => { expect(0.2 + 0.1).toBe(0.3); // Fails! });
It fails because in JavaScript, 0.2 + 0.1
is actually 0.30000000000000004
.
For example, this test passes with a precision of 5 digits:
test('adding works sanely with decimals', () => { expect(0.2 + 0.1).toBeCloseTo(0.3, 5); });
Because floating point errors are the problem that toBeCloseTo
solves, it does not support big integer values.
.toBeDefined()
Use .toBeDefined
to check that a variable is not undefined. For example, if you want to check that a function fetchNewFlavorIdea()
returns something, you can write:
test('there is a new flavor idea', () => { expect(fetchNewFlavorIdea()).toBeDefined(); });
You could write expect(fetchNewFlavorIdea()).not.toBe(undefined)
, but it’s better practice to avoid referring to undefined
directly in your code.
.toBeFalsy()
Use .toBeFalsy
when you don’t care what a value is and you want to ensure a value is false in a boolean context. For example, let’s say you have some application code that looks like:
drinkSomeLaCroix(); if (!getErrors()) { drinkMoreLaCroix(); }
You may not care what getErrors
returns, specifically — it might return false
, null
, or 0
, and your code would still work. So if you want to test there are no errors after drinking some La Croix, you could write:
test('drinking La Croix does not lead to errors', () => { drinkSomeLaCroix(); expect(getErrors()).toBeFalsy(); });
In JavaScript, there are six falsy values: false
, 0
, ''
, null
, undefined
, and NaN
. Everything else is truthy.
.toBeGreaterThan(number | bigint)
Use toBeGreaterThan
to compare received > expected
for number or big integer values. For example, test that ouncesPerCan()
returns a value of more than 10 ounces:
test('ounces per can is more than 10', () => { expect(ouncesPerCan()).toBeGreaterThan(10); });
.toBeGreaterThanOrEqual(number | bigint)
Use toBeGreaterThanOrEqual
to compare received >= expected
for number or big integer values. For example, test that ouncesPerCan()
returns a value of at least 12 ounces:
test('ounces per can is at least 12', () => { expect(ouncesPerCan()).toBeGreaterThanOrEqual(12); });
.toBeLessThan(number | bigint)
Use toBeLessThan
to compare received < expected
for number or big integer values. For example, test that ouncesPerCan()
returns a value of less than 20 ounces:
test('ounces per can is less than 20', () => { expect(ouncesPerCan()).toBeLessThan(20); });
.toBeLessThanOrEqual(number | bigint)
Use toBeLessThanOrEqual
to compare received <= expected
for number or big integer values. For example, test that ouncesPerCan()
returns a value of at most 12 ounces:
test('ounces per can is at most 12', () => { expect(ouncesPerCan()).toBeLessThanOrEqual(12); });
.toBeInstanceOf(Class)
Use .toBeInstanceOf(Class)
to check that an object is an instance of a class. This matcher uses instanceof
underneath.
class A {} expect(new A()).toBeInstanceOf(A); expect(() => {}).toBeInstanceOf(Function); expect(new A()).toBeInstanceOf(Function); // throws
.toBeNull()
.toBeNull()
is the same as .toBe(null)
but the error messages are a bit nicer. So use .toBeNull()
when you want to check that something is null.
function bloop() { return null; } test('bloop returns null', () => { expect(bloop()).toBeNull(); });
.toBeTruthy()
Use .toBeTruthy
when you don’t care what a value is and you want to ensure a value is true in a boolean context. For example, let’s say you have some application code that looks like:
drinkSomeLaCroix(); if (thirstInfo()) { drinkMoreLaCroix(); }
You may not care what thirstInfo
returns, specifically — it might return true
or a complex object, and your code would still work. So if you want to test that thirstInfo
will be truthy after drinking some La Croix, you could write:
test('drinking La Croix leads to having thirst info', () => { drinkSomeLaCroix(); expect(thirstInfo()).toBeTruthy(); });
In JavaScript, there are six falsy values: false
, 0
, ''
, null
, undefined
, and NaN
. Everything else is truthy.
.toBeUndefined()
Use .toBeUndefined
to check that a variable is undefined. For example, if you want to check that a function bestDrinkForFlavor(flavor)
returns undefined
for the 'octopus'
flavor, because there is no good octopus-flavored drink:
test('the best drink for octopus flavor is undefined', () => { expect(bestDrinkForFlavor('octopus')).toBeUndefined(); });
You could write expect(bestDrinkForFlavor('octopus')).toBe(undefined)
, but it’s better practice to avoid referring to undefined
directly in your code.
.toBeNaN()
Use .toBeNaN
when checking a value is NaN
.
test('passes when value is NaN', () => { expect(NaN).toBeNaN(); expect(1).not.toBeNaN(); });
.toContain(item)
Use .toContain
when you want to check that an item is in an array. For testing the items in the array, this uses ===
, a strict equality check. .toContain
can also check whether a string is a substring of another string.
For example, if getAllFlavors()
returns an array of flavors and you want to be sure that lime
is in there, you can write:
test('the flavor list contains lime', () => { expect(getAllFlavors()).toContain('lime'); });
This matcher also accepts others iterables such as strings, sets, node lists and HTML collections.
.toContainEqual(item)
Use .toContainEqual
when you want to check that an item with a specific structure and values is contained in an array. For testing the items in the array, this matcher recursively checks the equality of all fields, rather than checking for object identity.
describe('my beverage', () => { test('is delicious and not sour', () => { const myBeverage = {delicious: true, sour: false}; expect(myBeverages()).toContainEqual(myBeverage); }); });
.toEqual(value)
Use .toEqual
to compare recursively all properties of object instances (also known as «deep» equality). It calls Object.is
to compare primitive values, which is even better for testing than ===
strict equality operator.
For example, .toEqual
and .toBe
behave differently in this test suite, so all the tests pass:
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); }); });
:::tip
.toEqual
won’t perform a deep equality check for two errors. Only the message
property of an Error is considered for equality. It is recommended to use the .toThrow
matcher for testing against errors.
:::
If differences between properties do not help you to understand why a test fails, especially if the report is large, then you might move the comparison into the expect
function. For example, use equals
method of Buffer
class to assert whether or not buffers contain the same content:
- rewrite
expect(received).toEqual(expected)
asexpect(received.equals(expected)).toBe(true)
- rewrite
expect(received).not.toEqual(expected)
asexpect(received.equals(expected)).toBe(false)
.toMatch(regexp | string)
Use .toMatch
to check that a string matches a regular expression.
For example, you might not know what exactly essayOnTheBestFlavor()
returns, but you know it’s a really long string, and the substring grapefruit
should be in there somewhere. You can test this with:
describe('an essay on the best flavor', () => { test('mentions grapefruit', () => { expect(essayOnTheBestFlavor()).toMatch(/grapefruit/); expect(essayOnTheBestFlavor()).toMatch(new RegExp('grapefruit')); }); });
This matcher also accepts a string, which it will try to match:
describe('grapefruits are healthy', () => { test('grapefruits are a fruit', () => { expect('grapefruits').toMatch('fruit'); }); });
.toMatchObject(object)
Use .toMatchObject
to check that a JavaScript object matches a subset of the properties of an object. It will match received objects with properties that are not in the expected object.
You can also pass an array of objects, in which case the method will return true only if each object in the received array matches (in the toMatchObject
sense described above) the corresponding object in the expected array. This is useful if you want to check that two arrays match in their number of elements, as opposed to arrayContaining
, which allows for extra elements in the received array.
You can match properties against values or against matchers.
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?)
This ensures that a value matches the most recent snapshot. Check out the Snapshot Testing guide for more information.
You can provide an optional propertyMatchers
object argument, which has asymmetric matchers as values of a subset of expected properties, if the received value will be an object instance. It is like toMatchObject
with flexible criteria for a subset of properties, followed by a snapshot test as exact criteria for the rest of the properties.
You can provide an optional hint
string argument that is appended to the test name. Although Jest always appends a number at the end of a snapshot name, short descriptive hints might be more useful than numbers to differentiate multiple snapshots in a single it
or test
block. Jest sorts snapshots by name in the corresponding .snap
file.
.toMatchInlineSnapshot(propertyMatchers?, inlineSnapshot)
Ensures that a value matches the most recent snapshot.
You can provide an optional propertyMatchers
object argument, which has asymmetric matchers as values of a subset of expected properties, if the received value will be an object instance. It is like toMatchObject
with flexible criteria for a subset of properties, followed by a snapshot test as exact criteria for the rest of the properties.
Jest adds the inlineSnapshot
string argument to the matcher in the test file (instead of an external .snap
file) the first time that the test runs.
Check out the section on Inline Snapshots for more info.
.toStrictEqual(value)
Use .toStrictEqual
to test that objects have the same types as well as structure.
Differences from .toEqual
:
- Keys with
undefined
properties are checked. e.g.{a: undefined, b: 2}
does not match{b: 2}
when using.toStrictEqual
. - Array sparseness is checked. e.g.
[, 1]
does not match[undefined, 1]
when using.toStrictEqual
. - Object types are checked to be equal. e.g. A class instance with fields
a
andb
will not equal a literal object with fieldsa
andb
.
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?)
Also under the alias: .toThrowError(error?)
Use .toThrow
to test that a function throws when it is called. For example, if we want to test that drinkFlavor('octopus')
throws, because octopus flavor is too disgusting to drink, we could write:
test('throws on octopus', () => { expect(() => { drinkFlavor('octopus'); }).toThrow(); });
:::tip
You must wrap the code in a function, otherwise the error will not be caught and the assertion will fail.
:::
You can provide an optional argument to test that a specific error is thrown:
- regular expression: error message matches the pattern
- string: error message includes the substring
- error object: error message is equal to the message property of the object
- error class: error object is instance of class
For example, let’s say that drinkFlavor
is coded like this:
function drinkFlavor(flavor) { if (flavor == 'octopus') { throw new DisgustingFlavorError('yuck, octopus flavor'); } // Do some other stuff }
We could test this error gets thrown in several ways:
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?)
Use .toThrowErrorMatchingSnapshot
to test that a function throws an error matching the most recent snapshot when it is called.
You can provide an optional hint
string argument that is appended to the test name. Although Jest always appends a number at the end of a snapshot name, short descriptive hints might be more useful than numbers to differentiate multiple snapshots in a single it
or test
block. Jest sorts snapshots by name in the corresponding .snap
file.
For example, let’s say you have a drinkFlavor
function that throws whenever the flavor is 'octopus'
, and is coded like this:
function drinkFlavor(flavor) { if (flavor == 'octopus') { throw new DisgustingFlavorError('yuck, octopus flavor'); } // Do some other stuff }
The test for this function will look this way:
test('throws on octopus', () => { function drinkOctopus() { drinkFlavor('octopus'); } expect(drinkOctopus).toThrowErrorMatchingSnapshot(); });
And it will generate the following snapshot:
exports[`drinking flavors throws on octopus 1`] = `"yuck, octopus flavor"`;
Check out React Tree Snapshot Testing for more information on snapshot testing.
.toThrowErrorMatchingInlineSnapshot(inlineSnapshot)
Use .toThrowErrorMatchingInlineSnapshot
to test that a function throws an error matching the most recent snapshot when it is called.
Jest adds the inlineSnapshot
string argument to the matcher in the test file (instead of an external .snap
file) the first time that the test runs.
Check out the section on Inline Snapshots for more info.
Asymmetric Matchers
expect.anything()
expect.anything()
matches anything but null
or undefined
. You can use it inside toEqual
or toBeCalledWith
instead of a literal value. For example, if you want to check that a mock function is called with a non-null argument:
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)
matches anything that was created with the given constructor or if it’s a primitive that is of the passed type. You can use it inside toEqual
or toBeCalledWith
instead of a literal value. For example, if you want to check that a mock function is called with a number:
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)
matches a received array which contains all of the elements in the expected array. That is, the expected array is a subset of the received array. Therefore, it matches a received array which contains elements that are not in the expected array.
You can use it instead of a literal value:
- in
toEqual
ortoBeCalledWith
- to match a property in
objectContaining
ortoMatchObject
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)
matches a received array which does not contain all of the elements in the expected array. That is, the expected array is not a subset of the received array.
It is the inverse of 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?)
is useful when comparing floating point numbers in object properties or array item. If you need to compare a number, please use .toBeCloseTo
instead.
The optional numDigits
argument limits the number of digits to check after the decimal point. For the default value 2
, the test criterion is Math.abs(expected - received) < 0.005 (that is, 10 ** -2 / 2)
.
For example, this test passes with a precision of 5 digits:
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)
matches any received object that recursively matches the expected properties. That is, the expected object is a subset of the received object. Therefore, it matches a received object which contains properties that are present in the expected object.
Instead of literal property values in the expected object, you can use matchers, expect.anything()
, and so on.
For example, let’s say that we expect an onPress
function to be called with an Event
object, and all we need to verify is that the event has event.x
and event.y
properties. We can do that with:
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)
matches any received object that does not recursively match the expected properties. That is, the expected object is not a subset of the received object. Therefore, it matches a received object which contains properties that are not in the expected object.
It is the inverse of 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)
matches the received value if it is a string that contains the exact expected string.
expect.not.stringContaining(string)
expect.not.stringContaining(string)
matches the received value if it is not a string or if it is a string that does not contain the exact expected string.
It is the inverse of 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)
matches the received value if it is a string that matches the expected string or regular expression.
You can use it instead of a literal value:
- in
toEqual
ortoBeCalledWith
- to match an element in
arrayContaining
- to match a property in
objectContaining
ortoMatchObject
This example also shows how you can nest multiple asymmetric matchers, with expect.stringMatching
inside the 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(string | regexp)
expect.not.stringMatching(string | regexp)
matches the received value if it is not a string or if it is a string that does not match the expected string or regular expression.
It is the inverse of 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)); }); });
Assertion Count
expect.assertions(number)
expect.assertions(number)
verifies that a certain number of assertions are called during a test. This is often useful when testing asynchronous code, in order to make sure that assertions in a callback actually got called.
For example, let’s say that we have a function doAsync
that receives two callbacks callback1
and callback2
, it will asynchronously call both of them in an unknown order. We can test this with:
test('doAsync calls both callbacks', () => { expect.assertions(2); function callback1(data) { expect(data).toBeTruthy(); } function callback2(data) { expect(data).toBeTruthy(); } doAsync(callback1, callback2); });
The expect.assertions(2)
call ensures that both callbacks actually get called.
expect.hasAssertions()
expect.hasAssertions()
verifies that at least one assertion is called during a test. This is often useful when testing asynchronous code, in order to make sure that assertions in a callback actually got called.
For example, let’s say that we have a few functions that all deal with state. prepareState
calls a callback with a state object, validateState
runs on that state object, and waitOnState
returns a promise that waits until all prepareState
callbacks complete. We can test this with:
test('prepareState prepares a valid state', () => { expect.hasAssertions(); prepareState(state => { expect(validateState(state)).toBeTruthy(); }); return waitOnState(); });
The expect.hasAssertions()
call ensures that the prepareState
callback actually gets called.
Extend Utilities
expect.addEqualityTesters(testers)
You can use expect.addEqualityTesters
to add your own methods to test if two objects are equal. For example, let’s say you have a class in your code that represents volume and it supports determining if two volumes using different units are equal or not. You may want toEqual
(and other equality matchers) to use this custom equality method when comparing to Volume classes. You can add a custom equality tester to have toEqual
detect and apply custom logic when comparing Volume classes:
// 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 false; } else { return undefined; } } 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 false; } else { return undefined; } } 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')); });
Custom equality testers API
Custom testers are functions that return either the result (true
or false
) of comparing the equality of the two given arguments or undefined
if tester does not handle the given the objects and wants to delegate equality to other testers (for example, the built in equality testers).
Custom testers are called with 3 arguments: the two objects to compare and the array of custom testers (used for recursive testers, see section below).
These helper functions and properties can be found on this
inside a custom tester:
this.equals(a, b, customTesters?)
This is a deep-equality function that will return true
if two objects have the same values (recursively). It optionally takes a list of custom equality testers to apply to the deep equality checks. If you use this function, pass through the custom testers your tester is given so further equality checks equals
applies can also use custom testers the test author may have configured. See the example in the Recursive custom equality testers section for more details.
Matchers vs Testers
Matchers are methods available on expect
, for example expect().toEqual()
. toEqual
is a matcher. A tester is a method used by matchers that do equality checks to determine if objects are the same.
Custom matchers are good to use when you want to provide a custom assertion that test authors can use in their tests. For example, the toBeWithinRange
example in the expect.extend
section is a good example of a custom matcher. Sometimes a test author may want to assert two numbers are exactly equal and should use toBe
. Other times however, a test author may want to allow for some flexibility in their test and toBeWithinRange
may be a more appropriate assertion.
Custom equality testers are good to use for globally extending Jest matchers to apply custom equality logic for all equality comparisons. Test authors can’t turn on custom testers for certain assertions and turn off for others (a custom matcher should be used instead if that behavior is desired). For example, defining how to check if two Volume
objects are equal for all matchers would be a good custom equality tester.
Recursive custom equality testers
If you custom equality testers is testing objects with properties you’d like to do deep equality with, you should use the this.equals
helper available to equality testers. This equals
method is the same deep equals method Jest uses internally for all of its deep equality comparisons. Its the method that invokes your custom equality tester. It accepts an array of custom equality testers as a third argument. Custom equality testers are also given an array of custom testers as their third argument. Pass this argument into the third argument of equals
so that any further equality checks deeper in your object can also take advantage of custom equality testers.
For example, let’s say you have a Book
class that contains an array of Author
classes and both of these classes have custom testers. The Book
custom tester would want to do a deep equality check on the array of Author
s and pass in the custom testers given to it so the Author
s custom equality tester is applied:
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 false; } else { return undefined; } } 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 false; } else { return undefined; } } expect.addEqualityTesters([areAuthorsEqual, areBooksEqual]);
:::note
Remember to define your equality testers as regular functions and not arrow functions in order to access the tester context helpers (e.g. this.equals
).
:::
expect.addSnapshotSerializer(serializer)
You can call expect.addSnapshotSerializer
to add a module that formats application-specific data structures.
For an individual test file, an added module precedes any modules from snapshotSerializers
configuration, which precede the default snapshot serializers for built-in JavaScript types and for React elements. The last module added is the first module tested.
import serializer from 'my-serializer-module'; expect.addSnapshotSerializer(serializer); // affects expect(value).toMatchSnapshot() assertions in the test file
If you add a snapshot serializer in individual test files instead of adding it to snapshotSerializers
configuration:
- You make the dependency explicit instead of implicit.
- You avoid limits to configuration that might cause you to eject from create-react-app.
See configuring Jest for more information.
expect.extend(matchers)
You can use expect.extend
to add your own matchers to Jest. For example, let’s say that you’re testing a number utility library and you’re frequently asserting that numbers appear within particular ranges of other numbers. You could abstract that into a toBeWithinRange
matcher:
import {expect} from '@jest/globals'; function toBeWithinRange(actual, floor, ceiling) { if ( typeof actual !== 'number' || typeof floor !== 'number' || typeof ceiling !== 'number' ) { throw new Error('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 Error('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), }); });
:::tip
The type declaration of the matcher can live in a .d.ts
file or in an imported .ts
module (see JS and TS examples above respectively). If you keep the declaration in a .d.ts
file, make sure that it is included in the program and that it is a valid module, i.e. it has at least an empty export {}
.
:::
:::tip
Instead of importing toBeWithinRange
module to the test file, you can enable the matcher for all tests by moving the expect.extend
call to a setupFilesAfterEnv
script:
import {expect} from '@jest/globals'; // remember to export `toBeWithinRange` as well import {toBeWithinRange} from './toBeWithinRange'; expect.extend({ toBeWithinRange, });
:::
Async Matchers
expect.extend
also supports async matchers. Async matchers return a Promise so you will need to await the returned value. Let’s use an example matcher to illustrate the usage of them. We are going to implement a matcher called toBeDivisibleByExternalValue
, where the divisible number is going to be pulled from an external source.
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(); });
Custom Matchers API
Matchers should return an object (or a Promise of an object) with two keys. pass
indicates whether there was a match or not, and message
provides a function with no arguments that returns an error message in case of failure. Thus, when pass
is false, message
should return the error message for when expect(x).yourMatcher()
fails. And when pass
is true, message
should return the error message for when expect(x).not.yourMatcher()
fails.
Matchers are called with the argument passed to expect(x)
followed by the arguments passed to .yourMatcher(y, z)
:
expect.extend({ yourMatcher(x, y, z) { return { pass: true, message: () => '', }; }, });
These helper functions and properties can be found on this
inside a custom matcher:
this.isNot
A boolean to let you know this matcher was called with the negated .not
modifier allowing you to display a clear and correct matcher hint (see example code).
this.promise
A string allowing you to display a clear and correct matcher hint:
'rejects'
if matcher was called with the promise.rejects
modifier'resolves'
if matcher was called with the promise.resolves
modifier''
if matcher was not called with a promise modifier
this.equals(a, b, customTesters?)
This is a deep-equality function that will return true
if two objects have the same values (recursively). It optionally takes a list of custom equality testers to apply to the deep equality checks (see this.customTesters
below).
this.expand
A boolean to let you know this matcher was called with an expand
option. When Jest is called with the --expand
flag, this.expand
can be used to determine if Jest is expected to show full diffs and errors.
this.utils
There are a number of helpful tools exposed on this.utils
primarily consisting of the exports from jest-matcher-utils
.
The most useful ones are matcherHint
, printExpected
and printReceived
to format the error messages nicely. For example, take a look at the implementation for the toBe
matcher:
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) + 'nn' + `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) + 'nn' + (diffString && diffString.includes('- Expect') ? `Difference:nn${diffString}` : `Expected: ${this.utils.printExpected(expected)}n` + `Received: ${this.utils.printReceived(received)}`) ); }; return {actual: received, message, pass}; }, });
This will print something like this:
expect(received).toBe(expected) Expected value to be (using Object.is): "banana" Received: "apple"
When an assertion fails, the error message should give as much signal as necessary to the user so they can resolve their issue quickly. You should craft a precise failure message to make sure users of your custom assertions have a good developer experience.
this.customTesters
If your matcher does a deep equality check using this.equals
, you may want to pass user provided custom testers to this.equals
. The custom equality testers that the user has provided using the addEqualityTesters
API are available on this property. The built-in Jest matchers pass this.customTesters
(along with other built-in testers) to this.equals
to do deep equality, and your custom matchers may want to do the same.
Custom snapshot matchers
To use snapshot testing inside of your custom matcher you can import jest-snapshot
and use it from within your matcher.
Here’s a snapshot matcher that trims a string to store for a given length, .toMatchTrimmedSnapshot(length)
:
const {toMatchSnapshot} = require('jest-snapshot'); expect.extend({ toMatchTrimmedSnapshot(received, length) { return toMatchSnapshot.call( this, received.substring(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"`; */
It’s also possible to create custom matchers for inline snapshots, the snapshots will be correctly added to the custom matchers. However, inline snapshot will always try to append to the first argument or the second when the first argument is the property matcher, so it’s not possible to accept custom arguments in the custom matchers.
const {toMatchInlineSnapshot} = require('jest-snapshot'); expect.extend({ toMatchTrimmedInlineSnapshot(received, ...rest) { return toMatchInlineSnapshot.call(this, received.substring(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
If your custom inline snapshot matcher is async i.e. uses async
—await
you might encounter an error like «Multiple inline snapshots for the same call are not supported». Jest needs additional context information to find where the custom inline snapshot matcher was used to update the snapshots properly.
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"`); */ });
Bail out
Usually jest
tries to match every snapshot that is expected in a test.
Sometimes it might not make sense to continue the test if a prior snapshot failed. For example, when you make snapshots of a state-machine after various transitions you can abort the test once one transition produced the wrong state.
In that case you can implement a custom snapshot matcher that throws on the first mismatch instead of collecting every mismatch.
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"`); });
Время прочтения
7 мин
Просмотры 60K
Здравствуй, Хабр! Данное руководство является первой частью в запланированном цикле статей про такой замечательный фреймворк для тестирования как Jest. Материал будет полезен новичкам и тем, кто только знакомится с тестированием, и хотел бы изучить этот фреймворк. В первой части мы разберём: как начать работу с jest, как написать простой тест, и какие есть методы для сопоставления проверяемых значение с ожидаемыми. Кому интересно — добро пожаловать под кат!
Что такое Jest?
Как указано на домашней странице проекта:
Jest — это восхитительная среда тестирования JavaScript с упором на простоту.
И действительно, Jest очень простой. Он не требует дополнительных настроек, легкий в понимании и применении, а так же имеет довольно хорошую документацию. Отлично подходит для проектов использующих Node, React, Angular, Vue, Babel, TypeScript и не только.
Также он имеет открытый исходный код и поддерживается компанией Facebook.
Установка
Для установки Jest в ваш проект выполните:
npm install --save-dev jest
Если вы используете yarn:
yarn add --dev jest
После установки можете обновить секцию scripts вашего package.json:
“scripts” : {
“test”: “jest”
}
С помощью такого простого вызова мы уже можем запустить наши тесты (на самом деле jest потребует существование хотя бы одного теста).
Также можно установить глобально (но так делать я бы не рекомендовал, так как по мне глобальная установка модулей является плохой практикой):
npm install jest --global
И соответственно для yarn:
yarn global add jest
После этого вы можете использовать jest непосредственно из командной строки.
При помощи вызова команды jest —init в корне проекта, ответив на несколько вопросов, вы получите файл с настройками jest.config.js. Или можно добавить конфигурацию прямиком в ваш package.json. Для этого добавьте в корень json ключ «jest» и в соответствующем ему объекте можете добавлять необходимые вам настройки. Сами опции мы разберем позже. На данном этапе в этом нет необходимости, поскольку jest можно использовать «сходу», без дополнительных конфигураций.
Первый тест
Давайте создадим файл first.test.js и напишем наш первый тест:
//first.test.js
test('My first test', () => {
expect(Math.max(1, 5, 10)).toBe(10);
});
И запустим наши тесты с помощью npm run test или непосредственно командой jest (если он установлен глобально). После запуска мы увидим отчет о прохождении тестов.
<b>PASS</b> ./first.test.js
✓ My first test (1 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.618 s, estimated 1 s
Давайте «сломаем» наш тест и запустим jest повторно:
//first.test.js
test('My first test', () => {
expect(Math.max(1, 5, 10)).toBe(5);
});
Как мы видим, теперь наш тест не проходит проверки. Jest отображает подробную информацию о том, где возникла проблема, какой был ожидаемый результат, и что мы получили вместо него.
Теперь давайте разберём код самого теста. Функция test используется для создания нового теста. Она принимает три аргумента (в примере мы использовали вызов с двумя аргументами). Первый — строка с названием теста, его jest отобразит в отчете. Второй — функция, которая содержит логику нашего теста. Также можно использовать 3-й аргумент — таймаут. Он является не обязательным, а его значение по умолчанию составляет 5 секунд. Задаётся в миллисекундах. Этот параметр необходим когда мы работаем с асинхронным кодом и возвращаем из функции теста промис. Он указывает как долго jest должен ждать разрешения промиса. По истечению этого времени, если промис не был разрешен — jest будет считать тест не пройденным. Подробнее про работу с асинхронными вызовами будет в следующих частях. Также вместо test() можно использовать it(). Разницы между такими вызовами нету. it() это просто алиас на функцию test().
Внутри функции теста мы сначала вызываем expect(). Ему мы передаем значение, которое хотим проверить. В нашем случае, это результат вызова Math.max(1, 5, 10). expect() возвращает объект «обертку», у которой есть ряд методов для сопоставления полученного значения с ожидаемым. Один из таких методов мы и использовали — toBe.
Давайте разберем основные из этих методов:
- toBe() — подходит, если нам надо сравнивать примитивные значения или является ли переданное значение ссылкой на тот же объект, что указан как ожидаемое значение. Сравниваются значения при помощи Object.is(). В отличие от === это дает возможность отличать 0 от -0, проверить равенство NaN c NaN.
- toEqual() — подойдёт, если нам необходимо сравнить структуру более сложных типов. Он сравнит все поля переданного объекта с ожидаемым. Проверит каждый элемент массива. И сделает это рекурсивно по всей вложенности.
test('toEqual with objects', () => { expect({ foo: 'foo', subObject: { baz: 'baz' } }) .toEqual({ foo: 'foo', subObject: { baz: 'baz' } }); // Ок expect({ foo: 'foo', subObject: { num: 0 } }) .toEqual({ foo: 'foo', subObject: { baz: 'baz' } }); // А вот так ошибка. }); test('toEqual with arrays', () => { expect([11, 19, 5]).toEqual([11, 19, 5]); // Ок expect([11, 19, 5]).toEqual([11, 19]); // Ошибка });
- toContain() — проверят содержит массив или итерируемый объект значение. Для сравнения используется оператор ===.
const arr = ['apple', 'orange', 'banana']; expect(arr).toContain('banana'); expect(new Set(arr)).toContain('banana'); expect('apple, orange, banana').toContain('banana');
- toContainEqual() — проверяет или содержит массив элемент с ожидаемой структурой.
expect([{a: 1}, {b: 2}]).toContainEqual({a: 1});
- toHaveLength() — проверяет или свойство length у объекта соответствует ожидаемому.
expect([1, 2, 3, 4]).toHaveLength(4); expect('foo').toHaveLength(3); expect({ length: 1 }).toHaveLength(1);
- toBeNull() — проверяет на равенство с null.
- toBeUndefined() — проверяет на равенство с undefined.
- toBeDefined() — противоположность toBeUndefined. Проверяет или значение !== undefined.
- toBeTruthy() — проверяет или в булевом контексте значение соответствует true. Тоесть любые значения кроме false, null, undefined, 0, NaN и пустых строк.
- toBeFalsy() — противоположность toBeTruthy(). Проверяет или в булевом контексте значение соответствует false.
- toBeGreaterThan() и toBeGreaterThanOrEqual() — первый метод проверяет или переданное числовое значение больше, чем ожидаемое >, второй проверяет больше или равно ожидаемому >=.
- toBeLessThan() и toBeLessThanOrEqual() — противоположность toBeGreaterThan() и toBeGreaterThanOrEqual()
- toBeCloseTo() — удобно использовать для чисел с плавающей запятой, когда вам не важна точность и вы не хотите, чтобы тест зависел от незначительной разницы в дроби. Вторым аргументом можно передать до какого знака после запятой необходима точность при сравнении.
const num = 0.1 + 0.2; // 0.30000000000000004 expect(num).toBeCloseTo(0.3); expect(Math.PI).toBeCloseTo(3.14, 2);
- toMatch() — проверяет соответствие строки регулярному выражению.
expect('Banana').toMatch(/Ba/);
- toThrow() — используется в случаях, когда надо проверить исключение. Можно проверить как сам факт ошибки, так и проверить на выброс исключения определенного класса, либо по сообщению ошибки, либо по соответствию сообщения регулярному выражению.
function funcWithError() { throw new Error('some error'); } expect(funcWithError).toThrow(); expect(funcWithError).toThrow(Error); expect(funcWithError).toThrow('some error'); expect(funcWithError).toThrow(/some/);
- not — это свойство позволяет сделать проверки на НЕравенство. Оно предоставляет объект, который имеет все методы перечисленные выше, но работать они будут наоборот.
expect(true).not.toBe(false); expect({ foo: 'bar' }).not.toEqual({}); function funcWithoutError() {} expect(funcWithoutError).not.toThrow();
Давайте напишем пару простых тестов. Для начала создадим простой модуль, который будет содержать несколько методов для работы с окружностями.
// src/circle.js
const area = (radius) => Math.PI * radius ** 2;
const circumference = (radius) => 2 * Math.PI * radius;
module.exports = { area, circumference };
Далее добавим тесты:
// tests/circle.test.js
const circle = require('../src/circle');
test('Circle area', () => {
expect(circle.area(5)).toBeCloseTo(78.54);
expect(circle.area()).toBeNaN();
});
test('Circumference', () => {
expect(circle.circumference(11)).toBeCloseTo(69.1, 1);
expect(circle.circumference()).toBeNaN();
});
В этих тестах мы проверили результат работы 2-х методов — area и circumference. При помощи метода toBeCloseTo мы сверились с ожидаемым результатом. В первом случае мы проверили или вычисляемая площадь круга с радиусом 5 приблизительно равна 78.54, при этом разница с полученым значением (оно составит 78.53981633974483) не большая и тест будет засчитан. Во втором мы указали, что нас интересует проверка с точностью до 1 знака после запятой. Также мы вызвали наши методы без аргументов и проверили результат с помощью toBeNaN. Поскольку результат их выполнения будет NaN, то и тесты будут пройдены успешно.
Разберём ещё один пример. Создадим функцию, которая будет фильтровать массив продуктов по цене:
// src/productFilter.js
const byPriceRange = (products, min, max) =>
products.filter(item => item.price >= min && item.price <= max);
module.exports = { byPriceRange };
И добавим тест:
// tests/product.test.js
const productFilter = require('../src/producFilter');
const products = [
{ name: 'onion', price: 12 },
{ name: 'tomato', price: 26 },
{ name: 'banana', price: 29 },
{ name: 'orange', price: 38 }
];
test('Test product filter by range', () => {
const FROM = 15;
const TO = 30;
const filteredProducts = productFilter.byPriceRange(products, FROM, TO);
expect(filteredProducts).toHaveLength(2);
expect(filteredProducts).toContainEqual({ name: 'tomato', price: 26 });
expect(filteredProducts).toEqual([{ name: 'tomato', price: 26 }, { name: 'banana', price: 29 }]);
expect(filteredProducts[0].price).toBeGreaterThanOrEqual(FROM);
expect(filteredProducts[1].price).toBeLessThanOrEqual(TO);
expect(filteredProducts).not.toContainEqual({ name: 'orange', price: 38 });
});
В этом тесте мы проверям результат работы функии byRangePrice. Сначала мы проверили соответствие длины полученого массива ожидаемой — 2. Следующая проверка требует, чтобы в массиве находился элемент — { name: ‘tomato’, price: 26 }. Объект в массиве и объект переданный toContainEqual — это два разных объекта, а не ссылка на один и тот же. Но toContainEqual сверит каждое свойство. Так как оба объекта идентичные — проверка пройдет успешно. Далее мы используем toEqual для провеки структуры всего массива и его элементов. Методы toBeGreaterThanOrEqual и toBeLessThanOrEqual помогут нам проверить price первого и второго элемента массива. И, наконец, вызов not.toContainEqual сделает проверку, не содержится ли в массиве элемент — { name: ‘orange’, price: 38 }, которого по условию там быть не должно.
В данных примерах мы написали несколько простых тестов используя функции проверки описанные выше. В следующих частях мы разберём работу с асинхронным кодом, функции jest которые не затрагивались в этой части туториала, поговорим о его настройке и многое другое.
When you’re writing tests, you often need to check that values meet certain conditions. expect
gives you access to a number of «matchers» that let you validate different things.
Methods #
expect(value)
expect.extend(matchers)
this.isNot
this.utils
expect.anything()
expect.any(constructor)
expect.arrayContaining(array)
expect.assertions(number)
expect.stringContaining(string)
expect.stringMatching(regexp)
expect.objectContaining(object)
.not
.toBe(value)
.toHaveBeenCalled()
.toHaveBeenCalledTimes(number)
.toHaveBeenCalledWith(arg1, arg2, ...)
.toHaveBeenLastCalledWith(arg1, arg2, ...)
.toBeCloseTo(number, numDigits)
.toBeDefined()
.toBeFalsy()
.toBeGreaterThan(number)
.toBeGreaterThanOrEqual(number)
.toBeLessThan(number)
.toBeLessThanOrEqual(number)
.toBeInstanceOf(Class)
.toBeNull()
.toBeTruthy()
.toBeUndefined()
.toContain(item)
.toContainEqual(item)
.toEqual(value)
.toHaveLength(number)
.toMatch(regexpOrString)
.toMatchObject(object)
.toMatchSnapshot(optionalString)
.toThrow(error)
.toThrowErrorMatchingSnapshot()
Reference #
expect(value)
#
The expect
function is used every time you want to test a value. You will rarely call expect
by itself. Instead, you will use expect
along with a «matcher» function to assert something about a value.
It’s easier to understand this with an example. Let’s say you have a method bestLaCroixFlavor()
which is supposed to return the string 'grapefruit'
.
Here’s how you would test that:
test('the best flavor is grapefruit', () => { expect(bestLaCroixFlavor()).toBe('grapefruit'); });
In this case, toBe
is the matcher function. There are a lot of different matcher functions, documented below, to help you test different things.
The argument to expect
should be the value that your code produces, and any argument to the matcher should be the correct value. If you mix them up, your tests will still work, but the error messages on failing tests will look strange.
expect.extend(matchers)
#
You can use expect.extend
to add your own matchers to Jest. For example, let’s say that you’re testing a number theory library and you’re frequently asserting that numbers are divisible by other numbers. You could abstract that into a toBeDivisibleBy
matcher:
expect.extend({ toBeDivisibleBy(received, argument) { const pass = (received % argument == 0); if (pass) { return { pass: true, message: () => `expected ${received} not to be divisible by ${argument}`, } } else { return { pass: false, message: () => `expected ${received} to be divisible by ${argument}`, } } } }); test('even and odd numbers', () => { expect(100).toBeDivisibleBy(2); expect(101).not.toBeDivisibleBy(2); });
Matchers should return an object with two keys. pass
indicates whether there was a match or not, and message
provides a function with no arguments that returns an error message in case of failure. Thus, when pass
is false, message
should return the error message for when expect(x).yourMatcher()
fails. And when pass
is true, message
should return the error message for when expect(x).not.yourMatcher()
fails.
These helper functions can be found on this
inside a custom matcher:
this.isNot
#
A boolean to let you know this matcher was called with the negated .not
modifier allowing you to flip your assertion.
this.utils
#
There are a number of helpful tools exposed on this.utils
primarily consisting of the exports from jest-matcher-utils
.
The most useful ones are matcherHint
, printExpected
and printReceived
to format the error messages nicely. For example, take a look at the implementation for the toBe
matcher:
const diff = require('jest-diff'); expect.extend({ toBe(received, expected) { const pass = received === expected; const message = pass ? () => this.utils.matcherHint('.not.toBe') + 'nn' + `Expected value to not be (using ===):n` + ` ${this.utils.printExpected(expected)}n` + `Received:n` + ` ${this.utils.printReceived(received)}` : () => { const diffString = diff(expected, received, { expand: this.expand, }); return this.utils.matcherHint('.toBe') + 'nn' + `Expected value to be (using ===):n` + ` ${this.utils.printExpected(expected)}n` + `Received:n` + ` ${this.utils.printReceived(received)}` + (diffString ? `nnDifference:nn${diffString}` : ''); }; return {actual: received, message, pass}; }, });
This will print something like this:
expect(received).toBe(expected) Expected value to be (using ===): "banana" Received: "apple"
When an assertion fails, the error message should give as much signal as necessary to the user so they can resolve their issue quickly. You should craft a precise failure message to make sure users of your custom assertions have a good developer experience.
expect.anything()
#
expect.anything()
matches anything but null
or undefined
. You can use it inside toEqual
or toBeCalledWith
instead of a literal value. For example, if you want to check that a mock function is called with a non-null argument:
test('map calls its argument with a non-null argument', () => { let mock = jest.fn(); [1].map(mock); expect(mock).toBeCalledWith(expect.anything()); })
expect.any(constructor)
#
expect.any(constructor)
matches anything that was created with the given constructor. You can use it inside toEqual
or toBeCalledWith
instead of a literal value. For example, if you want to check that a mock function is called with a number:
function randocall(fn) { return fn(Math.floor(Math.random() * 6 + 1)); } test('randocall calls its callback with a number', () => { let mock = jest.fn(); randocall(mock); expect(mock).toBeCalledWith(expect.any(Number)); })
expect.arrayContaining(array)
#
expect.arrayContaining(array)
matches any array made up entirely of elements in the provided array. You can use it inside toEqual
or toBeCalledWith
instead of a literal value. For example, this code checks that rollDice
returns only valid numbers:
function rollDice(n) { let answer = []; for (let i = 0; i < n; i++) { answer.push(Math.floor(Math.random() * 6 + 1)); } return answer; } test('rollDice only returns valid numbers', () => { expect(rollDice(100)).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6])); })
expect.assertions(number)
#
expect.assertions(number)
verifies that a certain number of assertions are called during a test. This is often useful when testing asynchronous code, in order to make sure that assertions in a callback actually got called.
For example, let’s say that we have a few functions that all deal with state. prepareState
calls a callback with a state object, validateState
runs on that state object, and waitOnState
returns a promise that waits until all prepareState
callbacks complete. We can test this with:
test('prepareState prepares a valid state', () => { expect.assertions(1); prepareState((state) => { expect(validateState(state)).toBeTruthy(); }) return waitOnState(); })
The expect.assertions(1)
call ensures that the prepareState
callback actually gets called.
expect.stringContaining(string)
#
expect.stringContaining(string)
matches any string that contains the exact provided string.
expect.stringMatching(regexp)
#
expect.stringMatching(regexp)
matches any string that matches the provided regexp. You can use it inside toEqual
or toBeCalledWith
instead of a literal value. For example, let’s say you want to test that randomCoolNames()
only returns names that are cool:
function randomCoolName() { let lastName = ( 'TRFGBNMPLZ'[Math.floor(Math.random() * 10)] + 'aeiou'[Math.floor(Math.random() * 5)] + 'mnbvxdstrp'[Math.floor(Math.random() * 10)]); return 'Kevin ' + lastName; } function randomCoolNames() { let answer = []; for (let i = 0; i < 100; i++) { answer.push(randomCoolName()); } return answer; } test('randomCoolNames only returns cool names', () => { let coolRegex = /^Kevin/; expect(randomCoolNames).toEqual( expect.arrayContaining(expect.stringMatching(coolRegex))); });
This example also shows how you can nest multiple asymmetric matchers, with expect.stringMatching
inside the expect.arrayContaining
.
expect.objectContaining(object)
#
expect.objectContaining(object)
matches any object that recursively matches the provided keys. This is often handy in conjunction with other asymmetric matchers.
For example, let’s say that we expect an onPress
function to be called with an Event
object, and all we need to verify is that the event has event.x
and event.y
properties. We can do that with:
test('onPress gets called with the right thing', () => { let onPress = jest.fn(); simulatePresses(onPress); expect(onPress).toBeCalledWith(expect.objectContaining({ x: expect.any(Number), y: expect.any(Number), })); })
.not
#
If you know how to test something, .not
lets you test its opposite. For example, this code tests that the best La Croix flavor is not coconut:
test('the best flavor is not coconut', () => { expect(bestLaCroixFlavor()).not.toBe('coconut'); });
.toBe(value)
#
toBe
just checks that a value is what you expect. It uses ===
to check
strict equality.
For example, this code will validate some properties of the beverage
object:
const can = { ounces: 12, name: 'pamplemousse', }; describe('the can', () => { test('has 12 ounces', () => { expect(can.ounces).toBe(12); }); test('has a sophisticated name', () => { expect(can.name).toBe('pamplemousse'); }); });
Don’t use toBe
with floating-point numbers. For example, due to rounding, in JavaScript 0.2 + 0.1
is not strictly equal to 0.3
. If you have floating point numbers, try .toBeCloseTo
instead.
.toHaveBeenCalled()
#
Also under the alias: .toBeCalled()
Use .toHaveBeenCalled
to ensure that a mock function got called.
For example, let’s say you have a drinkAll(drink, flavor)
function that takes a drink
function and applies it to all available beverages. You might want to check that drink
gets called for 'lemon'
, but not for 'octopus'
, because 'octopus'
flavor is really weird and why would anything be octopus-flavored? You can do that with this test suite:
describe('drinkAll', () => { test('drinks something lemon-flavored', () => { let drink = jest.fn(); drinkAll(drink, 'lemon'); expect(drink).toHaveBeenCalled(); }); test('does not drink something octopus-flavored', () => { let drink = jest.fn(); drinkAll(drink, 'octopus'); expect(drink).not.toHaveBeenCalled(); }); });
.toHaveBeenCalledTimes(number)
#
Use .toHaveBeenCalledTimes
to ensure that a mock function got called exact number of times.
For example, let’s say you have a drinkEach(drink, Array<flavor>)
function that takes a drink
function and applies it to array of passed beverages. You might want to check that drink function was called exact number of times. You can do that with this test suite:
test('drinkEach drinks each drink', () => { let drink = jest.fn(); drinkEach(drink, ['lemon', 'octopus']); expect(drink).toHaveBeenCalledTimes(2); });
.toHaveBeenCalledWith(arg1, arg2, ...)
#
Also under the alias: .toBeCalledWith
Use .toHaveBeenCalledWith
to ensure that a mock function was called with specific arguments.
For example, let’s say that you can register a beverage with a register
function, and applyToAll(f)
should apply the function f
to all registered beverages. To make sure this works, you could write:
test('registration applies correctly to orange La Croix', () => { let beverage = new LaCroix('orange'); register(beverage); let f = jest.fn(); applyToAll(f); expect(f).toHaveBeenCalledWith(beverage); });
.toHaveBeenLastCalledWith(arg1, arg2, ...)
#
Also under the alias: .lastCalledWith(arg1, arg2, ...)
If you have a mock function, you can use .toHaveBeenLastCalledWith
to test what arguments it was last called with. For example, let’s say you have a applyToAllFlavors(f)
function that applies f
to a bunch of flavors, and you want to ensure that when you call it, the last flavor it operates on is 'mango'
. You can write:
test('applying to all flavors does mango last', () => { let drink = jest.fn(); applyToAllFlavors(drink); expect(drink).toHaveBeenLastCalledWith('mango'); });
.toBeCloseTo(number, numDigits)
#
Using exact equality with floating point numbers is a bad idea. Rounding means that intuitive things fail. For example, this test fails:
test('adding works sanely with simple decimals', () => { expect(0.2 + 0.1).toBe(0.3); });
It fails because in JavaScript, 0.2 + 0.1
is actually 0.30000000000000004
. Sorry.
Instead, use .toBeCloseTo
. Use numDigits
to control how many digits after the decimal point to check. For example, if you want to be sure that 0.2 + 0.1
is equal to 0.3
with a precision of 5 decimal digits, you can use this test:
test('adding works sanely with simple decimals', () => { expect(0.2 + 0.1).toBeCloseTo(0.3, 5); });
The default for numDigits
is 2, which has proved to be a good default in most cases.
.toBeDefined()
#
Use .toBeDefined
to check that a variable is not undefined. For example, if you just want to check that a function fetchNewFlavorIdea()
returns something, you can write:
test('there is a new flavor idea', () => { expect(fetchNewFlavorIdea()).toBeDefined(); });
You could write expect(fetchNewFlavorIdea()).not.toBe(undefined)
, but it’s better practice to avoid referring to undefined
directly in your code.
.toBeFalsy()
#
Use .toBeFalsy
when you don’t care what a value is, you just want to ensure a value is false in a boolean context. For example, let’s say you have some application code that looks like:
drinkSomeLaCroix(); if (!getErrors()) { drinkMoreLaCroix(); }
You may not care what getErrors
returns, specifically — it might return false
, null
, or 0
, and your code would still work. So if you want to test there are no errors after drinking some La Croix, you could write:
test('drinking La Croix does not lead to errors', () => { drinkSomeLaCroix(); expect(getErrors()).toBeFalsy(); });
In JavaScript, there are six falsy values: false
, 0
, ''
, null
, undefined
, and NaN
. Everything else is truthy.
.toBeGreaterThan(number)
#
To compare floating point numbers, you can use toBeGreaterThan
. For example, if you want to test that ouncesPerCan()
returns a value of more than 10 ounces, write:
test('ounces per can is more than 10', () => { expect(ouncesPerCan()).toBeGreaterThan(10); });
.toBeGreaterThanOrEqual(number)
#
To compare floating point numbers, you can use toBeGreaterThanOrEqual
. For example, if you want to test that ouncesPerCan()
returns a value of at least 12 ounces, write:
test('ounces per can is at least 12', () => { expect(ouncesPerCan()).toBeGreaterThanOrEqual(12); });
.toBeLessThan(number)
#
To compare floating point numbers, you can use toBeLessThan
. For example, if you want to test that ouncesPerCan()
returns a value of less than 20 ounces, write:
test('ounces per can is less than 20', () => { expect(ouncesPerCan()).toBeLessThan(20); });
.toBeLessThanOrEqual(number)
#
To compare floating point numbers, you can use toBeLessThanOrEqual
. For example, if you want to test that ouncesPerCan()
returns a value of at most 12 ounces, write:
test('ounces per can is at most 12', () => { expect(ouncesPerCan()).toBeLessThanOrEqual(12); });
.toBeInstanceOf(Class)
#
Use .toBeInstanceOf(Class)
to check that an object is an instance of a class. This matcher uses instanceof
underneath.
class A {} expect(new A()).toBeInstanceOf(A); expect(() => {}).toBeInstanceOf(Function); expect(new A()).toBeInstanceOf(Function);
.toBeNull()
#
.toBeNull()
is the same as .toBe(null)
but the error messages are a bit nicer. So use .toBeNull()
when you want to check that something is null.
function bloop() { return null; } test('bloop returns null', () => { expect(bloop()).toBeNull(); });
.toBeTruthy()
#
Use .toBeTruthy
when you don’t care what a value is, you just want to ensure a value is true in a boolean context. For example, let’s say you have some application code that looks like:
drinkSomeLaCroix(); if (thirstInfo()) { drinkMoreLaCroix(); }
You may not care what thirstInfo
returns, specifically — it might return true
or a complex object, and your code would still work. So if you just want to test that thirstInfo
will be truthy after drinking some La Croix, you could write:
test('drinking La Croix leads to having thirst info', () => { drinkSomeLaCroix(); expect(thirstInfo()).toBeTruthy(); });
In JavaScript, there are six falsy values: false
, 0
, ''
, null
, undefined
, and NaN
. Everything else is truthy.
.toBeUndefined()
#
Use .toBeUndefined
to check that a variable is undefined. For example, if you want to check that a function bestDrinkForFlavor(flavor)
returns undefined
for the 'octopus'
flavor, because there is no good octopus-flavored drink:
test('the best drink for octopus flavor is undefined', () => { expect(bestDrinkForFlavor('octopus')).toBeUndefined(); });
You could write expect(bestDrinkForFlavor('octopus')).toBe(undefined)
, but it’s better practice to avoid referring to undefined
directly in your code.
.toContain(item)
#
Use .toContain
when you want to check that an item is in a list. For testing the items in the list, this uses ===
, a strict equality check.
For example, if getAllFlavors()
returns a list of flavors and you want to be sure that lime
is in there, you can write:
test('the flavor list contains lime', () => { expect(getAllFlavors()).toContain('lime'); });
.toContainEqual(item)
#
Use .toContainEqual
when you want to check that an item is in a list.
For testing the items in the list, this matcher recursively checks the equality of all fields, rather than checking for object identity.
describe('my beverage', () => { test('is delicious and not sour', () => { const myBeverage = {delicious: true, sour: false}; expect(myBeverages()).toContainEqual(myBeverage); }); });
.toEqual(value)
#
Use .toEqual
when you want to check that two objects have the same value. This matcher recursively checks the equality of all fields, rather than checking for object identity. For example, toEqual
and toBe
behave differently in this test suite, so all the tests pass:
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); }); });
.toHaveLength(number)
#
Use .toHaveLength
to check that an object has a .length
property and it is set to a certain numeric value.
This is especially useful for checking arrays or strings size.
expect([1, 2, 3]).toHaveLength(3); expect('abc').toHaveLength(3); expect('').not.toHaveLength(5);
.toMatch(regexpOrString)
#
Use .toMatch
to check that a string matches a regular expression.
For example, you might not know what exactly essayOnTheBestFlavor()
returns, but you know it’s a really long string, and the substring grapefruit
should be in there somewhere. You can test this with:
describe('an essay on the best flavor', () => { test('mentions grapefruit', () => { expect(essayOnTheBestFlavor()).toMatch(/grapefruit/); expect(essayOnTheBestFlavor()).toMatch(new RegExp('grapefruit')); }) })
This matcher also accepts a string, which it will try to match:
describe('grapefruits are healthy', () => { test('grapefruits are a fruit', () => { expect('grapefruits').toMatch('fruit'); }) })
.toMatchObject(object)
#
Use .toMatchObject
to check that a JavaScript object matches a subset of the properties of an object.
const houseForSale = { bath: true, kitchen: { amenities: ['oven', 'stove', 'washer'], area: 20, wallColor: 'white' }, bedrooms: 4 }; const desiredHouse = { bath: true, kitchen: { amenities: ['oven', 'stove', 'washer'], wallColor: 'white' } }; test('the house has my desired features', () => { expect(houseForSale).toMatchObject(desiredHouse); });
.toMatchSnapshot(optionalString)
#
This ensures that a value matches the most recent snapshot. Check out the Snapshot Testing guide for more information.
You can also specify an optional snapshot name. Otherwise, the name is inferred from the test.
Note: While snapshot testing is most commonly used with React components, any serializable value can be used as a snapshot.
.toThrow(error)
#
Also under the alias: .toThrowError(error)
Use .toThrow
to test that a function throws when it is called. For example, if we want to test that drinkFlavor('octopus')
throws, because octopus flavor is too disgusting to drink, we could write:
test('throws on octopus', () => { expect(() => { drinkFlavor('octopus'); }).toThrow(); });
If you want to test that a specific error gets thrown, you can provide an argument to toThrow
. The argument can be a string for the error message, a class for the error, or a regex that should match the error. For example, let’s say that drinkFlavor
is coded like this:
function drinkFlavor(flavor) { if (flavor == 'octopus') { throw new DisgustingFlavorError('yuck, octopus flavor'); } }
We could test this error gets thrown in several ways:
test('throws on octopus', () => { function drinkOctopus() { drinkFlavor('octopus'); } expect(drinkOctopus).toThrowError('yuck, octopus flavor'); expect(drinkOctopus).toThrowError(/yuck/); expect(drinkOctopus).toThrowError(DisgustingFlavorError); });
.toThrowErrorMatchingSnapshot()
#
Use .toThrowErrorMatchingSnapshot
to test that a function throws a error matching the most recent snapshot when it is called. For example, let’s say you have a drinkFlavor
function that throws whenever the flavor is 'octopus'
, and is coded like this:
function drinkFlavor(flavor) { if (flavor == 'octopus') { throw new DisgustingFlavorError('yuck, octopus flavor'); } }
The test for this function will look this way:
test('throws on octopus', () => { function drinkOctopus() { drinkFlavor('octopus'); } expect(drinkOctopus).toThrowErrorMatchingSnapshot(); });
And it will generate the following snapshot:
exports[`drinking flavors throws on octopus 1`] = `"yuck, octopus flavor"`;
Check out React Tree Snapshot Testing for more information on snapshot testing.
Whenever you are looking to test an error thrown by a function in Jest, you want to pass the function to the expect
, rather than invoking the function. Take a look at the following examples:
const functionWithError = param => {
throw new Error()
}
it('should throw an error', () => {
expect(functionWithError).toThrow(Error)
})
Copied to clipboard!
We have a mock function and we want to test whether it throws the error we are expecting. We can do this by simply passing the function to the expect without actually invoking it, and calling the toThrow
method on it with the passed error.
But what if you have to call the function, for example, to provide parameters? In this case, you can wrap the function into an anonymous function:
// 🟢 Do
expect(() => functionWithError()).toThrow(Error)
// 🔴 Don’t
expect(functionWithError()).toThrow(Error)
expect(functionWithError('param')).toThrow(Error)
Copied to clipboard!
Notice that if you try to call the function directly inside the expect
, it will fail the test as the error is not caught and the assertion will fail. You can also test other types of errors by passing the correct error to toThrow
:
expect(functionWithError()).toThrow(EvalError)
expect(functionWithError()).toThrow(RangeError)
expect(functionWithError()).toThrow(ReferenceError)
expect(functionWithError()).toThrow(SyntaxError)
expect(functionWithError()).toThrow(TypeError)
expect(functionWithError()).toThrow(URIError)
expect(functionWithError()).toThrow(AggregateError)
Pass the correct type of error to test different types
Copied to clipboard!
Lastly, if you would like to test the error message itself, toThrow
also accepts a string so you can test both the type and message:
const functionWithError = () => {
throw new TypeError('Custom Error')
}
it('should throw an error', () => {
expect(functionWithError).toThrow(TypeError)
expect(functionWithError).toThrow('Custom Error')
})
Copied to clipboard!
How to Mock process.env in Jest
Unit testing environment-specific parts in your application
Learn how you can properly mock environment variables in your Jest tests to correctly test environment-specific parts in your application.
Jest is a Javascript Testing Framework by Facebook. It is used most commonly for unit testing. Unit testing is when you provide input to a unit of code(usually, a function) and match the output with the expected output.
Jest Features:
- zero config: As we will see later in this article, close to none configuration is required to get started with writing tests and deploying them. However, a config file can be supplied to the test suite as well.
- snapshots: Jest has the ability to enable snapshot testing as well. Essentially, the snapshots are matched with the saved snapshot and check for matching functionality.
- isolated tests: Jest tests are run parallelly to improve run time.
Setting up a jest project
- Install Jest using npm:
npm install jest. The -D keyword installs jest as dev dependency
- Project Structure:
In the project root directory, make a tests folder. This folder will store all the test files.
Note that the js files(which are to be tested) are mapped by their names.
For example, index.js is mapped to index.test.js. This index.test.js file is placed in the ‘tests’ folder. This is the conventional project structure.
Start testing:
- To start, lets see a basic test workflow.
test – adding two pos nums
- To run the test, use the script
npm run test
- This will look for the test script mentioned in the package.json of the project.
pkg-json-test-script-ss. See test in scripts
We will use the ‘expect’ method to test our functions. The functions can also be tested using ‘describe’ and ‘it’.
A basic test: adding two positive nums and checking result.
//index.js testForAdd: (a, b) => { return (a + b) }, //index.test.js test('test adding two positive nums', function() { expect(testFns.testForAdd(4, 5)).toBe(9); });
When the ‘npm run test’ is run, the index.test.js file is gone through. Then the testForAdd function is run which is placed in the ‘testFns’ object. The toBe is used to ‘match’ the returned response from the test to what is expected. This ‘result matching’ leads to either a ‘fail’ or a ‘pass’.
test adding two pos nums
The following test will fail because of the ‘toBe(8)’.
//example of a failing test test('test adding two positive nums - failing test', function() { expect(testFns.testForAdd(4, 5)).toBe(8); });
Opposite of toBe: not.toBe()
The opposite of the ‘toBe’ matcher is created by simply prefixing it with ‘not.’
For example:
//test successful - test for opposite of a matcher. //The following test will 'Pass' if the returned value is not equal to 8. test('test adding two positive nums - failing test', function() { expect(testFns.testForAdd(4, 5)).not.toBe(8); });
Using ‘toBe’ with JS Objects:
Let’s think of a case where every field of a JS object is to be tested. Jest provides us a way to do this using ‘toEqual’. The ‘toEqual’ is a deep-matcher(checks every field and sub-fields possible).
//expect toEqual example - check every field's value // testFns.test_toEqual(gfgObj) test('check gfgObj toEqual', () => { let gfgObj = { name: "GFG" }; gfgObj['type'] = "company"; expect(gfgObj).toEqual({ name: "GFG", type: "company" }); });
Running the above test will ‘Pass’.
gfgobj toEqual
Another variation of doing this is matching two objects using the ‘toEqual’.
This is done so like this :
test('check gfgObj toEqual', () => { let gfgObj = { name: "GFG", type: "company", members: { employees: 100, contributors: 500 } }; let testObj = { name: "GFG", type: "company", members: { employees: 100, contributors: 500 } }; expect(gfgObj).toEqual(testObj); });
This test demonstrates the deep-matching feature of toEqual.
The above test passes as every key-pair in gfgObj matches with testObj.
gfgobj-testobj-toequal
toBeCloseTo – for floating point numbers and other approximate matches
//see here that a+b != c even though simple addition is taking place. > var a = 1.32 > undefined > var b = 2.31 > undefined > c = a+b; > 3.63 > var res = false; > if(c==(a+b)) {c=true;} > undefined > c > false
In such conditions, it is good to use ‘toBeCloseTo’ matcher from the Jest library.
test('floating point number - use toBeCloseTo instead', function() { // const num1 = 0.3; // const num2 = 0.2; const result = 9.31 + 9.2; expect(result).toBeCloseTo(18.51); })
The above test passes too.
matching truthy and falsy:
https://jestjs.io/docs/en/using-matchers#truthiness
A lot of times, tests are written to check for truthy and falsy values returned by ‘expect’.
Falsy values in JS.
Truthy values in JS.
//checking for truthy values - All the tests will return truthy. test('check for truthy', function() { const gfgObj = { first: null, second: undefined, third: false } expect(gfgObj.first).not.toBeTruthy(); // True - Pass expect(gfgObj.second).toBeUndefined(); // True - Pass expect(gfgObj.third).toBeFalsy(); // True - Pass })
The above test passes.
truthy-test-js-all-test-passing
However, if any of the ‘expect’ above fails, Jest returns meaningful error messages like below.
Note that in above case, if any of the ‘expect’-s fail, the test also completely fails.
truthy-test-js-one-expect-failing-so-test-failing
Matching Numbers:
//tests for Number matches test('test for numbers', function() { const result = 3 + 9; // expect(result).toBe(12); //the plain old matcher expect(result).not.toBeLessThan(10); // result > 10 expect(result).toBeLessThan(15); // result < 15 expect(result).not.toBeGreaterThan(15); // result 10 expect(result).toBeGreaterThanOrEqual(12); //result >= 12 // expect(result).not.toBeGreaterThanOrEqual(12); // result == 12, this Fails // expect(result).toBeLessThanOrEqual(10); // result >= 10, this Fails })
number matching using jest
Testing values contained in arrays:
We can also test if particular values are contained in an array. Note that this test will ‘Fail’ if at least one value is not present in the array. For example,
//testing arrays const gfgUsers = [ 'user1', 'user2', 'user3' ]; test('test for a value in gfgUsers', function() { // expect(gfgUsers).toContain('user2'); // expect(gfgUsers).not.toContain('user2'); //expect array containing expect(gfgUsers).toEqual(expect.arrayContaining(['user1', 'user3'])); })
Th above test passes as user1 and user3 are present in gfgUsers.
user1-and-user3-in-gfgUsers
However, the following test will fail because ‘user4’ is not present in gfgUsers.
//testing arrays const gfgUsers = [ 'user1', 'user2', 'user3' ]; test('test for a value in gfgUsers', function() { //expect array containing expect(gfgUsers).toEqual(expect.arrayContaining(['user1', 'user4'])); })
arraycontaining-fail-due-to-user4-absence
Testing using Regex:
test('string match tests - toMatch - used for regex-es', function() { const str = 'GeeksforGeeks'; // expect(str).toMatch(/f/); // expect(str).toMatch(/Z/); //you can create more complex Regexes const str1 = 'This is a test paragraph. I wrote it.' expect(str1).toMatch(/[pP][hH][.]/); //matches 'ph.' in the word 'paragraph' })
tomatch-matching-ph-in-paragraph
Extending the Matchers
Jest also has the provision to extend its ‘Matchers’ functionality, which is accomplished using the ‘expect.extend()’ keyword. The .extend() function is passed matchers as objects.
Syntax: expect.extend({matcher1, matcher2}) ;
For example, if we want to build a matcher that checks for a phrase presence in a string:
expect.extend({ stringPresent(receivedString, phraseString) { bool phrasePresent = true; var re = new RegExp(phraseString); if (re.test(receivedString)) { phrasePresent = true; } else { phrasePresent = false; } if (phrasePresent === true) { return { message: () => `string present`, pass: true, }; } else { return { message: () => `string absent`, pass: false, }; } }, });
Dealing with exceptions
We can also check the types of errors that a unit of code throws. We can check the error thrown by name, message, object, etc.
Syntax:expect( fnName() ).toThrow( error );
The error parameter/argument is optional here.
Let’s suppose that we want to test a function by the message of the error thrown.
function testGFGMessage() { throw new Error('this is testGFGMessage function error'); } test('testing testGFGMessage', function(){ expect(testGFGMessage).toThrow('this is testGFGMessage function error'); })
There are many other ways to throw Errors and check for them. A detailed reference can be found here.
Skipping/Running a subset of tests
https://jestjs.io/docs/en/api#testskipname-fn
Jest also has a provision for skipping specific tests while running the test suite.
To implement it, simply use the ‘skip’ keyword. For example,
function addFn(num1, num2){ return num1 + num2; } test.skip('skip test example - skipping the addition test', () => { expect(addFn(2, 3)).toBe(5); });
The opposite of this is implementing only a subset of tests, which is achieved by using the ‘only’ keyword. For example,
function addFn(num1, num2){ return num1 + num2; } test.only('skip test example - skipping the addition test', () => { expect(addFn(2, 3)).toBe(5); });
Когда вы пишете тесты, вам часто нужно проверить, соответствуют ли значения определенным условиям. expect
дает вам доступ к ряду «сопоставителей», которые позволяют вам проверять разные вещи.
Чтобы узнать о дополнительных сопоставителях Jest, поддерживаемых сообществом Jest, ознакомьтесь с jest-extended
.
Methods
expect(value)
expect.extend(matchers)
expect.anything()
expect.any(constructor)
expect.arrayContaining(array)
expect.assertions(number)
expect.closeTo(number, numDigits?)
expect.hasAssertions()
expect.not.arrayContaining(array)
expect.not.objectContaining(object)
expect.not.stringContaining(string)
expect.not.stringMatching(string | regexp)
expect.objectContaining(object)
expect.stringContaining(string)
expect.stringMatching(string | regexp)
expect.addSnapshotSerializer(serializer)
.not
.resolves
.rejects
.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)
Reference
expect(value)
Функция expect
используется каждый раз, когда вы хотите проверить значение. Вы редко будете вызывать expect
само по себе. Вместо этого вы будете использовать expect
вместе с функцией «сопоставления», чтобы что-то утверждать о значении.
Это проще понять на примере. Допустим, у вас есть метод bestLaCroixFlavor()
который должен возвращать строку 'grapefruit'
. Вот как это проверить:
test('the best flavor is grapefruit', () => { expect(bestLaCroixFlavor()).toBe('grapefruit'); });
В этом случае toBe
сопоставления toBe . Существует множество различных функций сопоставления, которые описаны ниже, чтобы помочь вам тестировать разные вещи.
Аргумент expect
должен быть значением , которое производит ваш код, и любой аргумент согласовани должен быть правильным значением. Если вы их перепутаете, ваши тесты все равно будут работать, но сообщения об ошибках при неудачных тестах будут выглядеть странно.
expect.extend(matchers)
Вы можете использовать expect.extend
, чтобы добавлять в Jest собственные сопоставления. Например, предположим, что вы тестируете библиотеку утилит для чисел и часто утверждаете, что числа появляются в определенных диапазонах других чисел. Вы можете абстрагировать это в сопоставлении toBeWithinRange
:
expect.extend({ toBeWithinRange(received, floor, ceiling) { const pass = received >= floor && received <= ceiling; if (pass) { return { message: () => `expected ${received} not to be within range ${floor} - ${ceiling}`, pass: true, }; } else { return { message: () => `expected ${received} to be within range ${floor} - ${ceiling}`, pass: false, }; } }, }); test('numeric ranges', () => { expect(100).toBeWithinRange(90, 110); expect(101).not.toBeWithinRange(0, 100); expect({apples: 6, bananas: 3}).toEqual({ apples: expect.toBeWithinRange(1, 10), bananas: expect.not.toBeWithinRange(11, 20), }); });
note
Например, в TypeScript при использовании @types/jest
вы можете объявить новый toBeWithinRange
в импортированном модуле следующим образом:
interface CustomMatchers<R = unknown> { toBeWithinRange(floor: number, ceiling: number): R; } declare global { namespace jest { interface Expect extends CustomMatchers {} interface Matchers<R> extends CustomMatchers<R> {} interface InverseAsymmetricMatchers extends CustomMatchers {} } }
Async Matchers
expect.extend
также поддерживает асинхронные сопоставления. Асинхронные сопоставители возвращают обещание, поэтому вам нужно будет дождаться возвращенного значения. Давайте использовать пример сопоставления, чтобы проиллюстрировать их использование. Мы собираемся реализовать сопоставление под названием 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 пользовательских матчей
Сопоставители должны возвращать объект (или обещание объекта) с двумя ключами. 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
значение, чтобы вы знали, что этот сопоставитель был вызван с модификатором negated .not , позволяющим отображать четкую и правильную подсказку сопоставления (см. Пример кода).
this.promise
Строка,позволяющая отобразить четкую и правильную подсказку о матчере:
-
'rejects'
, если сопоставление было.rejects
модификатором обещания .rejects -
'resolves'
если сопоставление было.resolves
модификатором обещания .resolves -
''
если сопоставление не было вызвано с модификатором обещания
this.equals(a, b)
Это функция глубокого равенства, которая вернет true
если два объекта имеют одинаковые значения (рекурсивно).
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 ? () => this.utils.matcherHint('toBe', undefined, undefined, options) + 'nn' + `Expected: not ${this.utils.printExpected(expected)}n` + `Received: ${this.utils.printReceived(received)}` : () => { const diffString = diff(expected, received, { expand: this.expand, }); return ( this.utils.matcherHint('toBe', undefined, undefined, options) + 'nn' + (diffString && diffString.includes('- Expect') ? `Difference:nn${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"
Когда утверждение не удается,сообщение об ошибке должно давать столько сигнала,сколько необходимо пользователю,чтобы он мог быстро решить свою проблему.Вы должны создать точное сообщение об ошибке,чтобы убедиться,что пользователи ваших пользовательских утверждений имеют хороший опыт работы с разработчиками.
Пользовательские снимки матчей
Чтобы использовать тестирование снимков внутри вашего настраиваемого сопоставителя, вы можете импортировать jest-snapshot
и использовать его из своего сопоставителя.
Вот средство сопоставления снимков, которое обрезает строку для сохранения заданной длины, .toMatchTrimmedSnapshot(length)
:
const {toMatchSnapshot} = require('jest-snapshot'); expect.extend({ toMatchTrimmedSnapshot(received, length) { return toMatchSnapshot.call( this, received.substring(0, length), 'toMatchTrimmedSnapshot', ); }, }); it('stores only 10 characters', () => { expect('extra long string oh my gerd').toMatchTrimmedSnapshot(10); });
Также можно создавать пользовательские матчики для инлайн-снимков,при этом снимки будут корректно добавляться в пользовательские матчики.Однако inline snapshot всегда будет пытаться добавлять к первому аргументу или второму,если первый аргумент-это property matcher,поэтому невозможно принимать пользовательские аргументы в пользовательских matchers.
const {toMatchInlineSnapshot} = require('jest-snapshot'); expect.extend({ toMatchTrimmedInlineSnapshot(received, ...rest) { return toMatchInlineSnapshot.call(this, received.substring(0, 10), ...rest); }, }); it('stores only 10 characters', () => { expect('extra long string oh my gerd').toMatchTrimmedInlineSnapshot(); });
async
Если ваш пользовательский встроенный сопоставитель снимков является асинхронным, то есть использует async
— await
, вы можете столкнуться с ошибкой типа «Несколько встроенных снимков для одного и того же вызова не поддерживаются». Jest нужна дополнительная контекстная информация, чтобы найти, где пользовательское встроенное сопоставление снимков использовалось для правильного обновления снимков.
const {toMatchInlineSnapshot} = require('jest-snapshot'); expect.extend({ async toMatchObservationInlineSnapshot(fn, ...rest) { this.error = new Error(); const observation = await observe(async () => { await fn(); }); return toMatchInlineSnapshot.call(this, recording, ...rest); }, }); it('observes something', async () => { await expect(async () => { return 'async action'; }).toMatchTrimmedInlineSnapshot(); });
Bail out
Обычно jest
пытается сопоставить каждый снимок, который ожидается в тесте.
Иногда не имеет смысла продолжать тест,если предыдущий снимок оказался неудачным.Например,когда вы делаете снимки машины состояний после различных переходов,вы можете прервать тест,если один переход привел к неправильному состоянию.
В этом случае вы можете реализовать пользовательский snapshot matcher,который бросает при первом несоответствии вместо того,чтобы собирать каждое несоответствие.
const {toMatchInlineSnapshot} = require('jest-snapshot'); expect.extend({ toMatchStateInlineSnapshot(...args) { this.dontThrow = () => {}; return toMatchInlineSnapshot.call(this, ...args); }, }); let state = 'initial'; function transition() { if (state === 'INITIAL') { state = 'pending'; } else if (state === 'pending') { state = 'done'; } } it('transitions as expected', () => { expect(state).toMatchStateInlineSnapshot(`"initial"`); transition(); expect(state).toMatchStateInlineSnapshot(`"loading"`); transition(); expect(state).toMatchStateInlineSnapshot(`"done"`); });
expect.anything()
expect.anything()
соответствует чему угодно, кроме null
или undefined
. Вы можете использовать его внутри toEqual
или toBeCalledWith
вместо буквального значения. Например, если вы хотите проверить, что фиктивная функция вызывается с ненулевым аргументом:
test('map calls its argument with a non-null argument', () => { const mock = jest.fn(); [1].map(x => mock(x)); expect(mock).toBeCalledWith(expect.anything()); });
expect.any(constructor)
expect.any(constructor)
соответствует всему, что было создано с помощью данного конструктора, или если это примитив переданного типа. Вы можете использовать его внутри toEqual
или toBeCalledWith
вместо буквального значения. Например, если вы хотите проверить, вызывается ли фиктивная функция с числом:
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).toBeCalledWith(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).toBeCalledWith(expect.any(Number)); });
expect.arrayContaining(array)
expect.arrayContaining(array)
соответствует полученному массиву, который содержит все элементы ожидаемого массива. То есть ожидаемый массив — это подмножество полученного массива. Следовательно, он соответствует полученному массиву, который содержит элементы, которых нет в ожидаемом массиве.
Вы можете использовать его вместо буквального значения:
- в
toEqual
илиtoBeCalledWith
- для соответствия свойству в
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.assertions(number)
expect.assertions(number)
проверяет, что во время теста вызывается определенное количество утверждений. Это часто бывает полезно при тестировании асинхронного кода, чтобы убедиться, что утверждения в обратном вызове действительно вызываются.
Например, предположим, что у нас есть функция doAsync
, которая получает два обратных callback1
и callback2
, она будет асинхронно вызывать их оба в неизвестном порядке. Мы можем проверить это с помощью:
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.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.hasAssertions()
expect.hasAssertions()
проверяет, что во время теста вызывается хотя бы одно утверждение. Это часто бывает полезно при тестировании асинхронного кода, чтобы убедиться, что утверждения в обратном вызове действительно вызываются.
Например, предположим, что у нас есть несколько функций, которые все имеют дело с состоянием. prepareState
вызывает обратный вызов с объектом состояния, validateState
запускается для этого объекта состояния, а waitOnState
возвращает обещание, которое ожидает завершения всех prepareState
вызовов prepareState . Мы можем проверить это с помощью:
test('prepareState prepares a valid state', () => { expect.hasAssertions(); prepareState(state => { expect(validateState(state)).toBeTruthy(); }); return waitOnState(); });
В expect.hasAssertions()
гарантирует , что вызов prepareState
обратного вызова на самом деле вызывается.
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.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.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.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.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).toBeCalledWith( expect.objectContaining({ x: expect.any(Number), y: expect.any(Number), }), ); });
expect.stringContaining(string)
expect.stringContaining(string)
соответствует полученному значению, если это строка, содержащая точную ожидаемую строку.
expect.stringMatching(string | regexp)
expect.stringMatching(string | regexp)
соответствует полученному значению, если это строка, которая соответствует ожидаемой строке или регулярному выражению.
Вы можете использовать его вместо буквального значения:
- в
toEqual
илиtoBeCalledWith
- для соответствия элементу в
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.addSnapshotSerializer(serializer)
Вы можете вызвать expect.addSnapshotSerializer
, чтобы добавить модуль, форматирующий структуры данных для конкретного приложения.
Для отдельного тестового файла добавленный модуль предшествует любым модулям из конфигурации snapshotSerializers
, которые предшествуют сериализаторам моментальных снимков по умолчанию для встроенных типов JavaScript и для элементов React. Последний добавленный модуль — это первый протестированный модуль.
import serializer from 'my-serializer-module'; expect.addSnapshotSerializer(serializer);
Если вы добавляете сериализатор моментальных снимков в отдельные тестовые файлы вместо добавления его в конфигурацию snapshotSerializers
:
- Вы делаете зависимость явной,а не неявной.
- Вы избегаете ограничений конфигурации, из-за которых вы можете выйти из приложения create-react-app .
См. Дополнительную информацию в разделе « Настройка Jest» .
.not
Если вы знаете, как что-то тестировать, .not
позволяет вам протестировать противоположное. Например, этот код проверяет, что лучший вкус La Croix — не кокос:
test('the best flavor is not coconut', () => { expect(bestLaCroixFlavor()).not.toBe('coconut'); });
.resolves
Используйте resolves
, чтобы развернуть значение выполненного обещания, чтобы можно было связать любой другой сопоставитель. Если обещание отклонено, утверждение не выполняется.
Например, этот код проверяет, разрешается ли обещание и что в результате получается значение 'lemon'
:
test('resolves to lemon', () => { 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', () => { 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()
Используйте .toHaveBeenCalledWith
, чтобы убедиться, что фиктивная функция вызывается с определенными аргументами. Аргументы проверяются по тому же алгоритму, что .toEqual
.
Например, предположим, что у вас есть 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
и применяет ее к массиву переданных напитков. Вы можете проверить, вызывалась ли функция питья точное количество раз. Вы можете сделать это с помощью этого набора тестов:
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
функция, вы можете использовать .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
функция, вы можете использовать .toHaveBeenNthCalledWith, чтобы проверить, с какими аргументами она была вызвана. Например, предположим, что у вас есть 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'); });
note
n-й аргумент должен быть целым положительным числом,начиная с 1.
.toHaveReturned()
Также под псевдонимом: .toReturn()
Если у вас есть .toHaveReturned
функция, вы можете использовать .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)'); });
note
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 для объекта. Для проверки глубоко вложенных свойств в объекте вы можете использовать точечную нотацию или массив, содержащий keyPath для глубоких ссылок.
Вы можете предоставить необязательный аргумент value
для сравнения полученного значения свойства (рекурсивно для всех свойств экземпляров объекта, также известного как глубокое равенство, например сопоставление toEqual
).
В следующем примере содержится объект houseForSale
с вложенными свойствами. Мы используем toHaveProperty
для проверки существования и значений различных свойств в объекте.
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', () => { expect(houseForSale).toHaveProperty('bath'); expect(houseForSale).toHaveProperty('bedrooms', 4); expect(houseForSale).not.toHaveProperty('pool'); expect(houseForSale).toHaveProperty('kitchen.area', 20); expect(houseForSale).toHaveProperty('kitchen.amenities', [ 'oven', 'stove', 'washer', ]); expect(houseForSale).not.toHaveProperty('kitchen.open'); 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']); 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); });
Это не удается, потому что в JavaScript 0.2 + 0.1
фактически 0.30000000000000004
.
Например,этот тест проходит с точностью до 5 цифр:
test('adding works sanely with decimals', () => { expect(0.2 + 0.1).toBeCloseTo(0.3, 5); });
Поскольку ошибки с плавающей запятой — это проблема, toBeCloseTo
решает toBeCloseTo , он не поддерживает большие целочисленные значения.
.toBeDefined()
Используйте .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
, в частности — он может вернуть 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(number | bigint)
Используйте toBeGreaterThanOrEqual
для сравнения received >= expected
числовых или больших целочисленных значений. Например, проверьте, что ouncesPerCan()
возвращает значение не менее 12 унций:
test('ounces per can is at least 12', () => { expect(ouncesPerCan()).toBeGreaterThanOrEqual(12); });
.toBeLessThan(number | bigint)
Используйте toBeLessThan
для сравнения received < expected
числовых или больших целочисленных значений. Например, проверьте, что ouncesPerCan()
возвращает значение менее 20 унций:
test('ounces per can is less than 20', () => { expect(ouncesPerCan()).toBeLessThan(20); });
.toBeLessThanOrEqual(number | bigint)
Используйте toBeLessThanOrEqual
для сравнения received <= expected
числовых или больших целочисленных значений. Например, проверьте, что 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);
.toBeNull()
.toBeNull()
то же самое, что .toBe(null)
но сообщения об ошибках немного приятнее. Поэтому используйте .toBeNull()
если хотите проверить, что что-то имеет значение null.
function bloop() { return null; } test('bloop returns null', () => { expect(bloop()).toBeNull(); });
.toBeTruthy()
Используйте .toBeTruthy
, если вам все равно, что такое значение, и вы хотите убедиться, что значение истинно в логическом контексте. Например, допустим, у вас есть код приложения, который выглядит так:
drinkSomeLaCroix(); if (thirstInfo()) { drinkMoreLaCroix(); }
Вам может быть все равно, что возвращает thirstInfo
, в частности — он может вернуть true
или сложный объект, и ваш код все равно будет работать. Так что если вы хотите проверить , что thirstInfo
будет truthy выпив некоторое La Croix, вы могли бы написать:
test('drinking La Croix leads to having thirst info', () => { drinkSomeLaCroix(); expect(thirstInfo()).toBeTruthy(); });
В JavaScript существует шесть ложных значений: false
, 0
, ''
, null
, undefined
и NaN
. Все остальное правда.
.toBeUndefined()
Используйте .toBeUndefined
, чтобы проверить, что переменная не определена. Например, если вы хотите проверить, что функция bestDrinkForFlavor(flavor)
возвращает значение undefined
для вкуса 'octopus'
, потому что хорошего напитка со вкусом осьминога не существует:
test('the best drink for octopus flavor is undefined', () => { expect(bestDrinkForFlavor('octopus')).toBeUndefined(); });
Вы можете написать expect(bestDrinkForFlavor('octopus')).toBe(undefined)
, но лучше избегать ссылки на undefined
прямо в коде.
.toBeNaN()
Используйте .toBeNaN
при проверке значения NaN
.
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); }); });
tip
.toEqual
не будет выполнять глубокую проверку на равенство для двух ошибок. Только свойство message
объекта Error считается равным. Для проверки на наличие ошибок рекомендуется использовать сопоставитель .toThrow
.
Если различия между свойствами не помогают понять, почему тест не проходит, особенно если отчет большой, то вы можете перенести сравнение в функцию expect
. Например, используйте метод equals
класса Buffer
, чтобы проверить, содержат ли буферы одно и то же содержимое:
- перезаписать
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 подмножеству свойств объекта. Он сопоставляет полученные объекты со свойствами, которых нет в ожидаемом объекте.
Вы также можете передать массив объектов, и в этом случае метод вернет true, только если каждый объект в полученном массиве совпадает (в смысле toMatchObject
, описанном выше) с соответствующим объектом в ожидаемом массиве. Это полезно, если вы хотите проверить совпадение двух массивов по количеству элементов, в отличие от 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}
при использовании.toStrictEqual
. - Проверяется разреженность массива. например,
[, 1]
не соответствует[undefined, 1]
при использовании.toStrictEqual
. - Проверяется соответствие типов объектов. например, экземпляр класса с полями
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(); });
tip
Вы должны обернуть код в функцию,иначе ошибка не будет поймана и утверждение не пройдет.
Вы можете предоставить необязательный аргумент для проверки того,что конкретная ошибка выбрасывается:
- регулярное выражение: сообщение об ошибке соответствует шаблону
- строка: сообщение об ошибке включает подстроку
- объект ошибки: сообщение об ошибке равно свойству сообщения объекта
- класс ошибки: объект ошибки является экземпляром класса
Например, предположим, что drinkFlavor
имеет такой код:
function drinkFlavor(flavor) { if (flavor == 'octopus') { throw new DisgustingFlavorError('yuck, octopus flavor'); } }
Мы могли бы протестировать,что эта ошибка выбрасывается несколькими способами:
test('throws on octopus', () => { function drinkOctopus() { drinkFlavor('octopus'); } expect(drinkOctopus).toThrowError(/yuck/); expect(drinkOctopus).toThrowError('yuck'); expect(drinkOctopus).toThrowError(/^yuck, octopus flavor$/); expect(drinkOctopus).toThrowError(new Error('yuck, octopus flavor')); expect(drinkOctopus).toThrowError(DisgustingFlavorError); });
.toThrowErrorMatchingSnapshot(hint?)
Используйте .toThrowErrorMatchingSnapshot
, чтобы проверить, что функция выдает ошибку, соответствующую самому последнему снимку при ее вызове.
Вы можете указать необязательный аргумент строки hint
который будет добавлен к имени теста. Хотя Jest всегда добавляет число в конце имени снимки, короткие описательные подсказки могут быть более полезными , чем числа , чтобы различать несколько снимков в едином it
или test
блок. Jest сортирует снимки по имени в соответствующем файле .snap
.
Например, предположим, что у вас есть функция drinkFlavor
, которая срабатывает всякий раз, когда аромат 'octopus'
, и кодируется следующим образом:
function drinkFlavor(flavor) { if (flavor == 'octopus') { throw new DisgustingFlavorError('yuck, octopus flavor'); } }
Тест для этой функции будет выглядеть так:
test('throws on octopus', () => { function drinkOctopus() { drinkFlavor('octopus'); } expect(drinkOctopus).toThrowErrorMatchingSnapshot(); });
И он сгенерирует следующий снимок:
exports[`drinking flavors throws on octopus 1`] = `"yuck, octopus flavor"`;
Ознакомьтесь с React Tree Snapshot Testing для получения дополнительной информации о тестировании моментальных снимков.
.toThrowErrorMatchingInlineSnapshot(inlineSnapshot)
Используйте .toThrowErrorMatchingInlineSnapshot
, чтобы проверить, что функция выдает ошибку, соответствующую самому последнему снимку, когда она вызывается.
Jest добавляет строковый аргумент inlineSnapshot
к сопоставителю в тестовом файле (вместо внешнего файла .snap
) при первом запуске теста.
Ознакомьтесь с разделом « Встроенные снимки» для получения дополнительной информации.
Jest
29.0
-
Environment Variables
Jest устанавливает следующие переменные среды: Установите «тест», если это уже не что-то другое.
-
Манекен ES6 Класс
Jest можно использовать для имитации классов ES6, которые импортируются в файлы, которые вы хотите протестировать.
-
Jest Community
Сообщество вокруг Jest усердно работает над тем, чтобы сделать тестирование еще лучше.
-
Объект шутки
Объект jest автоматически находится в области видимости в каждом тестовом файле.