Why you should never use .toBe in Jest/Vitest
The Jared Wilcurt
Posted on July 21, 2020
Alright, this is going to be a short one. Let's just get to the point, what should you use instead, and why.
.toEqual()
is a better choice in every scenario.
Wait... I thought these were the same thing, just aliased?
Most do! And that right there is the problem. Here's the difference:
-
.toEqual
works based on deep equality -
.toBe
is literally just doing aObject.is(x, y)
under the hood. Which is slightly different, but basically the same asx === y
.
Here is an example where the two differ:
let x = { z: true };
let y = { z: true };
expect(x)
.toBe(y); // FALSE
expect(x)
.toEqual(y); // TRUE
Now sure, this confusion could have been avoided had these been named something like .toDeepEqual()
and .toStrictlyEqual()
. But that is not the world we live in! And it is unlikely to ever be, since they already have .toStrictEqual()
built in which actually is closer to a deep equal, than a strict equal (===
). Not at all confusing! 🤬
In most cases, you are comparing an actual value with a hard coded expectation.
test('Generates kitten', () => {
let kitten = generateKitten();
expect(kitten)
.toEqual({
fluffy: true,
type: 'kitty',
tail: true,
feet: 4
});
});
So in these cases, .toEqual()
gives you exactly what you want. It also shows a diff of the specific properties and values that do not match when a test fails.
But what about primatives? Like
undefined
,4
,'text'
,true
?
In these cases the .toEqual
and the .toBe
are equivalent, because the first thing they both check is if the values are strictly equal. So performance wise there is no difference. .toEqual
just handles more cases if the strict equality fails on non-primatives.
Okay, so if it doesn't matter, then I can use either for primitives?
You can... but you shouldn't. The naming of them is close enough that the subtle difference between when one should be used over the other isn't intuitive or obvious. You should default to using .toEqual
in all cases to prevent any confusion. For the same reason that I don't use ++x
or x++
in my codebases. I don't want to assume that the person that wrote that line of code understands the subtle differences between .toEqual
and .toBe
or the very subtle differences between Object.is
and ===
. It's much safer to always use .toEqual
so anyone in the codebase will follow this same approach. I'd rather just avoid the issue. Plus, consistency matters.
But what if I actually do want to test if something is a reference?
Sure, here's that hypothetical, and where people might wrongfully tell you to use .toBe
:
// Two players, both happen to have the same name and age
const player1 = { name: 'John', age: 25 };
const player2 = { name: 'John', age: 25 };
const players = [player1, player2];
function getFirstPlayer () {
return players[0];
}
test('getFirstPlayer', () => {
// USING TOBE
expect(getFirstPlayer())
.toBe(player1); // passes
expect(getFirstPlayer())
.not.toBe(player2); // passes
// USING TOEQUAL
expect(getFirstPlayer())
.toEqual(player1); // passes
expect(getFirstPlayer())
.not.toEqual(player2); // fails
});
In this example we actually want to know if a value is a reference. In most cases we don't want that, but here we do. So using .toBe
works, but it isn't obvious to others that we are using it to validate that something is a reference. So, even though the test passes, it's not really a good choice. We should make the intent of our code clear and obvious.
Here is a more obvious approach. (Note the use of .toEqual
)
test('getFirstPlayer', () => {
const result = getFirstPlayer();
expect(result === player1)
.toEqual(true);
expect(result === player2)
.toEqual(false);
});
Benefits of this approach:
- Intent is obvious, and Obvious is always better. It is reasonable to expect other developers to be familiar with how
===
works and to understand we are purposefully checking if both variables reference the same value. - Consistency. Using
.toEqual
for this will match the rest of your tests. It should be the default choice when doing matchers. - Reduces reliance on assumptions. (Assuming everyone knows how
.toBe
works. Which they don't. I literally had to correct the stack overflow post after looking up the Jest source code). - Reduces need for communication. (Verifying that everyone knows how
.toBe
, and by extension,Object.is
works.)
Ahh ok I get it. Reduce the cognitive overhead of choosing one over the other
Yes, .toBe
is never required, while .toEqual
often is. So use it by default in all cases and completely avoid .toBe
.
Credits:
Posted on July 21, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.