loading...
Cover image for Why you should never use .toBe in Jest

Why you should never use .toBe in Jest

thejaredwilcurt profile image The Jared Wilcurt Updated on ・4 min read

Patrick Stewart dressed as Hamlet with the text "toBe or toEqual, that is the question"

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 a Object.is(x, y) under the hood. Which is slightly different, but basically the same as x === 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 .deepEquals() and .strictlyEquals. 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.

Screenshot of a test failing that uses toEqual. Jest highlights the part that didn't match in the diff

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 I 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:

  1. 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.
  2. Consistency. Using .toEqual for this will match the rest of your tests. It should be the default choice when doing matchers.
  3. 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).
  4. 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 almost never required, while .toEqual often is. So use it by default in all cases and avoid .toBe.


Credits:

Posted on by:

thejaredwilcurt profile

The Jared Wilcurt

@thejaredwilcurt

Cross-Platform Desktop App (XPDA) Engineer, Senior Frontend Web Developer. Maintainer of Scout-App. Editor of XPDA.net.

Discussion

markdown guide