When starting out with Jest Matchers for unit testing, it’s common to hear “should I use toEqual or toStrictEqual?”.
Well, it depends on your object equality.
This article is dedicated to demonstrating the differences between toEqual
and toStrictEqual
, so you can make a decision which to use and when.
First, let’s start off with the follow basic data structure (an object literal):
const pizza = {
name: 'Flaming Sizzler',
price: 899,
};
And drop it in a simple test:
test('...', () => {
const pizza = {
name: 'Flaming Sizzler',
price: 899,
};
// ✅ PASS
expect(pizza).toEqual({
name: 'Flaming Sizzler',
price: 899,
});
});
Okay that makes sense, so what if we use a constructor
pattern to create a class
to test with?
test('...', () => {
class Pizza {
constructor(name, price) {
this.name = name;
this.price = price;
}
}
// ✅ PASS
expect(new Pizza('Flaming Sizzler', 899)).toEqual({
name: 'Flaming Sizzler',
price: 899,
});
});
Great! Uh, wait, is it great? No! Kinda… Maybe.
Despite the two objects being comparable on the top level public properties, their underlying implementation is NOT the same. Their prototypes differ.
There are two different data types. class Pizza
is a custom object, whereas our toEqual
is expecting an object literal.
These may “look” the same in your head, or developer tools console.log
, but they are not the same.
Meaning, this boils down to object equality…
So when we change to using toStrictEqual
, the error then presents itself:
test('...', () => {
class Pizza {
constructor(name, price) {
this.name = name;
this.price = price;
}
}
// ❌ FAIL
expect(new Pizza('Flaming Sizzler', 899)).toStrictEqual({
name: 'Flaming Sizzler',
price: 899,
});
});
Jest uses a deep equality check to determine the type differences.
You’ll need to use toStrictEqual
to verify that your data types are in fact the same.
You could also use the object literal approach combined with Jest’s .not
inversion:
test('...', () => {
class Pizza {
constructor(name, price) {
this.name = name;
this.price = price;
}
}
// ❌ FAIL
expect({ name: 'Flaming Sizzler', price: 899 }).not.toStrictEqual({
name: 'Flaming Sizzler',
price: 899,
});
// ✅ PASS
expect(new Pizza('Flaming Sizzler', 899)).not.toStrictEqual({
name: 'Flaming Sizzler',
price: 899,
});
});
The object literals that fail are the same, therefore error. The Pizza
type’s shape is strictly equal to the object literal, but it’s type is made optional in the test context.
One more thing with toEqual
is that it doesn’t take undefined
values (empty keys) into account, let’s add promo: undefined
and see the result:
test('...', () => {
const pizza = {
promo: undefined,
name: 'Flaming Sizzler',
price: 899,
};
// ✅ PASS
expect(pizza).toEqual({
name: 'Flaming Sizzler',
price: 899,
});
// ❌ FAIL
expect(pizza).toStrictEqual({
name: 'Flaming Sizzler',
price: 899,
});
});
Obviously we are hard-coding the const pizza
but in a real-world environment your data structure is likely going to be generated on-the-fly for your testing.
🚀 There’s so much more to JavaScript! Fast track your skills overnight with my masterclass JavaScript Courses and take your skills to the top.
Using toStrictEqual
should be your default go-to method instead of toEqual
for the above reasons.
Happy testing!