【问题标题】:Jasmine toEqual fails but printing the two objects are identicalJasmine toEqual 失败但打印两个对象是相同的
【发布时间】:2017-02-21 04:08:05
【问题描述】:

In this Jasmine test我比较了两个几乎相同的对象,唯一的区别是第二个对象多了一个未定义的成员。

describe('Testing', function () {

  it('should compare two objects', function () {


    var obj1 = {a: 1, b: 2 };

    var obj2 = {a: 1, b: 2, c: undefined };

    console.log(JSON.stringify(obj1));
    console.log(JSON.stringify(obj2));

    expect(obj1).toEqual(obj2);


  });

测试失败,但是使用JSON.stringify 打印两个对象会产生两个相同的输出。

{"a":1,"b":2}
{"a":1,"b":2}

浏览对象可以发现差异,但在复杂对象中,这并不容易。关于如何解决这个问题的任何建议?

【问题讨论】:

  • in javascript ... ({}) != ({}) 所以,在对象中没有任何机会
  • JSON 没有undefined 值,因此JSON.stringify 会忽略该类型的任何内容。这两个对象相等——一个有一个额外的属性——所以 Jasmine 很自然地告诉你。你在这里发现了什么问题?
  • 我将完全相同的代码(加上缺少的最终 });)复制到一个使用 react-scripts test --env=jsdom 执行测试的 React 项目中。这里的测试是用 jest,而不是 jasmine,但 jest 至少部分是建立在 jasmine 上的。当我运行测试时,它通过了,正如我认为的那样。如果我强制测试失败(通过将.not 添加到expect 语句中)只是为了查看输出,我会得到以下信息:Expected value to not equal: {"a": 1, "b": 2, "c": undefined}; Received: {"a": 1, "b": 2}
  • @JaromandaX,关于 JavaScript 中的对象(不)相等性,您是对的,但 Jasmine toMatch 方法比较对象属性值,与 ===== 不同。
  • 我上面关于toMatch的评论应该是关于toEqual的。更正此错字后,该评论仍然正确。通过引用比较对象需要toBe,而不是toEqual

标签: javascript jasmine jasmine2.0


【解决方案1】:

您的问题基于两个误解:

  1. obj2 中,c 是一个定义的属性,其值为undefined
  2. stringify() 没有按照规范序列化 undefined - 你的测试不安全

两个对象不相等

在茉莉花上toEqual()

toEqual 使用内部的util.equals(),它将逐个键地比较对象中所有 ab 中定义的 enumerable 键.

经过某种类型检查后,它进入了comparing the keys of the object

关于定义属性

查看 ECMAscript 规范。这个内部Put 方法在你创建对象字面量时被调用:

11.1.5 Object Initialiser

[...]

产生式 PropertyNameAndValueList : PropertyAssignment 的评估如下:

  1. 让 obj 成为创建新对象的结果,就像通过表达式 new Object() 一样,其中 Object 是具有该名称的标准内置构造函数。
  2. 令 propId 为评估 PropertyAssignment 的结果。
  3. 使用参数 propId.name、propId.descriptor 和 false 调用 obj 的 [[DefineOwnProperty]] 内部方法。
  4. 返回对象。

[...]

产生式 PropertyAssignment : PropertyName : AssignmentExpression 的评估如下:

  1. 让 propName 是评估 PropertyName 的结果。
  2. 设 exprValue 为评估 AssignmentExpression 的结果。
  3. 让 propValue 为 GetValue(exprValue)。
  4. 设 desc 为属性描述符{[[Value]]: propValue, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}

通过成员表达式类似定义属性:

8.12.5 [[Put]] (P, V, Throw) 当调用O的[[Put]]内部方法时,使用属性P、值V和布尔标志Throw,会采取以下步骤:

[...]

  1. 否则,在对象 O 上创建一个名为 P 的命名数据属性,如下所示

    一个。令 newDesc 为属性描述符 {[[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}。

    b.调用 O 的 [[DefineOwnProperty]] 内部方法,将 P、newDesc 和 Throw 作为参数传递。

DefineOwnProperty的实现,在8.12.9 [[DefineOwnProperty]] (P, Desc, Throw)中描述,不再赘述。还有MDN says,即使默认值也是undefined

检查:

> var obj2 = {a: 1, b: 2, c: undefined };
> obj2.hasOwnProperty("c");
< true

开启stringify()

看看ECMAscript spec on stringify()JSON spec

(来源:json.org)

这是 ECMAscript 规范的一部分:

  1. 其他 一个。令 K 是一个内部字符串列表,由 [[Enumerable]] 属性为 true 的 value 的所有自身属性的名称组成。字符串的顺序应与 Object.keys 标准内置函数使用的顺序相同。

他们说,该对象的枚举属性应该符合Object.keys() 的顺序(和结果)。让我们测试一下...

> var obj2 = {a: 1, b: 2, c: undefined };
> Object.keys(obj2);
< ["a", "b", "c"]

嗯,他们是对的!

然后有一个Str() 函数,它定义了处理undefined 值的行为。有几个If Type() ... 步骤,不适用于undefined 值,以

结尾
  1. 返回未定义

当被对象序列化器调用时:

  1. 对于 K 的每个元素 P。

    一个。令 strP 为使用参数 P 和值调用抽象操作 Str 的结果。

    b.如果strP不是未定义的

    [...]

【讨论】:

    【解决方案2】:

    正如 cmets 和其他答案之一中所解释的那样,两个对象都不相等。

    幸运的是,在 Jasmine 2.5 中,您可以使用 Custom equality tester(定义您自己的等号)来解决此问题:

    function customEquality(a, b) {
        let keys,
            key,
            equal = true;
        // Store unique list of keys over both objects
        keys = Object.keys(a).concat(Object.keys(b)).reduce(function(result, name) {
            if (!result.includes(name)) {
                result.push(name);
            }
            return result;
        }, []);
        for (key of keys) {
            // ignore when keys are defined in both objects, 
            // having the value undefined
            if (typeof a[key] === "undefined" && a.hasOwnProperty(key) &&
                typeof b[key] === "undefined" && b.hasOwnProperty(key)) {
                    continue;
            }
            equal = equal && b[key] === a[key];
        }
        return equal;
    }
    
    jasmine.addCustomEqualityTester(customEquality);
    

    将其插入到 beforeEach() 中,与您的测试在同一块中或在实际的 it() 中。

    基本测试器将忽略两个对象中存在的未定义值。如果一个对象中存在未定义的值,则它不会被视为相等。

    请注意,此测试器的行为与纯 toEqual() 有很大不同,因为它不比较数组或嵌套对象,也不比较 DOM 节点等其他对象类型。这只是一个指导你的例子。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-07
      • 1970-01-01
      • 1970-01-01
      • 2020-03-04
      • 1970-01-01
      • 2013-08-28
      • 1970-01-01
      相关资源
      最近更新 更多