在Jest中,'toBe'和'toEqual'有什么区别?

166

Jest文档中写到:

toBe只检查一个值是否符合预期。它使用 === 检查严格相等。

而对于toEqual

当你想检查两个对象是否具有相同的值时,请使用. toEqual。这个匹配器递归地检查所有字段的相等性,而不是检查对象标识——也就是“深度相等”。例如,在这个测试套件中,toEqualtoBe的行为不同,所以所有测试都通过了。

const x = { a: { b: 3 } };
const y = { a: { b: 3 } };

expect(x).toEqual(y);
expect(x).toBe(y);
在这种情况下,toEqual 通过了,但是toBe 失败了。我理解 toEqual 通过了是因为它进行了深度相等检查。为什么在这种情况下toBe失败了?
此外,在使用toBetoEqual时是否有最佳实践(不仅限于Jest,还包括其他测试框架)?

FYI:关于这两种方法的文档现在非常普遍:
  • https://jestjs.io/docs/en/expect#toequalvalue
  • https://jestjs.io/docs/en/expect#tobevalue 而且这两种方法都在内部使用 Object.is
- karfau
5个回答

140

这个测试失败的原因是xy是不同的实例,并且不相等,如(x === y) === false。你可以对字符串、数字或布尔值使用toBe,对其他类型的值请使用toEqual。例如:

x = 4 
y = 4
x === y // true

x = 'someString'
y = 'someString'
x === y // true

甚至空对象也不相等

x = {}
y = {}
x === y //false

4
使用 toBe 有没有任何理由? - Oliver Shaw
233
"toBe"或不"toBe",这是一个问题。 - Thomas Champion
144
@ThomasChampion 不是开玩笑……toBe()not.toBe(),这才是测试。 - lonix
27
theQuestion = toBe || !toBe; 可以翻译为“问题等于‘存在’或‘不存在’”。 - Coco
13
toBetoEqual 可以用于基本数据类型,但是 toBe 尤其适用于引用相等性,即当您想要断言它确实是对象的同一实例时。请注意,这句话已经被翻译为中文。 - bingles
显示剩余4条评论

27

假设有两个名字相同的玩家,他们都得了20分。

let player1 = {
    name: "Amit",
    score: 20,
}

let player2 = {
    name: "Amit",
    score: 20,
}

现在我有一个函数可以给我第一玩家。

function getFirstPlayer(player1,player2){
    return player1;
}

我该如何测试这个函数?

# 1st way
expect(getFirstPlayer(player1,player2)).toBe(player1); // Passes
expect(getFirstPlayer(player1,player2)).not.toBe(player2); // Passes

# 2nd way
expect(getFirstPlayer(player1,player2)).toEqual(player1); // Pases
expect(getFirstPlayer(player1,player2)).not.toEqual(player2); // Fails

toBe测试身份,toEqual测试特征。因此,孪生子可能具有相同的特征,但他们的真正身份互不相同。 根据这个函数的设计,我们应该使用toBe

现在我有另一个函数,可以增加玩家的得分。

function addScore(player,scoreToAdd){
    player.score += scoreToAdd;
}

我该如何测试这个函数?

# 1st way
addScore(player1,20);
expect(player1).toBe({name:"Amit", score:40});  // Fails

# 2nd way
addScore(player1,20);
expect(player1).toEqual({name:"Amit", score:40});  // Passes

你有没有注意到在第一种方法中,我们在右边传递了一个新的类似于实体的玩家。是否有可能player1具有与新创建的实体相同的标识?没有。因此,在这种情况下toBe将始终失败。

第二种方法是如此传递,因为在toEqual中我们正在比较特征。这里player1和新创建的实体具有相同的特征。

注意:在javascript的上下文中,像"Amit"这样的原始值本身就是标识。所以

expect("Amit").toBe("Amit") // Passes

我为特定情况编写了这个答案,当您获得身份和特征的想法后,可以在您的场景中实施它。


20

这全都关乎于对象引用。

.toBe 用于比较原始值或者检查对象实例的引用身份,而 toEqual 则寻找深度相等。

expect({ name: 'john doe' }).toEqual({ name: 'john doe'}); // PASSES
expect({ name: 'john doe' }).toBe({ name: 'john doe'});    // FAILS

第二个断言失败是因为它们是不同的实例,即使它们是深度相等的。请记住,对象字面语法创建基础对象的新实例。

let a = { name: 'john doe' };
let b = a;

在这里,赋值运算符将存储在变量a中的对象引用复制到b中。

expect(a).toBe(b); // PASSES

这个断言通过了,因为'a'和'b'指向同一个对象。请注意,{ name: 'john doe' }不是一个对象,而是创建一个对象的指令。对象存在于内存中,并通过存储在变量中的引用进行交互。


13

5

有些人说 .toBe()x === y 是一样的,但实际上它们是稍微不同的。当使用 expect(x).toBe(y) 时,Jest 使用 Object.is(x, y)

除非你正在验证一个值是否与引用相同(例如在检查某些东西是否被正确地深度克隆时),否则应始终使用 . toEqual()。即使在深层克隆示例中,我认为最好只需执行 expect(x === y).toEqual(true),以消除任何困惑。关于你试图做什么。

您不应该期望别人知道toBetoEqual之间的区别,甚至不知道Object.is存在以及它与===有何不同。为避免沟通问题和测试问题,请始终使用.toEqual而不要使用.toBe


我已经将这个更详细的版本写成了一篇博客文章:

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接