【问题标题】:Karma/Jasmine: Pretty printing object comparisonKarma/Jasmine:漂亮的打印对象比较
【发布时间】:2014-05-15 11:06:21
【问题描述】:

我目前正在为我的 Angular 项目使用 Karma 测试运行程序,以及 jasmine 测试框架。它工作得很好,但我有一个问题:当对象比较失败时,打印到控制台中的结果真的很难阅读,并且这些对象具有的属性越多就越难。示例:

Expected spy spy to have been called with [ { currentCareMoment : { ID : 5, Description : 'Late namiddag (16-20)', StartHour : 16, EndHour : 20 }, previousCareMoment : { ID : 4, Description : 'Namiddag (14-16)', StartHour : 14, EndHour : 16 } } ] but actual calls were [ { currentCareMoment : { ID : 6, Description : 'Avond (20-24)', StartHour : 20, EndHour : 24 }, previousCareMoment : { ID : 5, Description : 'Late namiddag (16-20)', StartHour : 16, EndHour : 20 } } ].

无论如何设置 Jasmine(我认为 Karma 与它无关)来打印更漂亮的对象?只是一些换行符和缩进已经是一个巨大的帮助。示例:

Expected spy spy to have been called with [ { 
  currentCareMoment : { 
    ID : 5, 
    Description : 'Late namiddag (16-20)', 
    StartHour : 16, 
    EndHour : 20 
  }, 
  previousCareMoment : { 
    ID : 4, 
    Description : 'Namiddag (14-16)', 
    StartHour : 14, 
    EndHour : 16 
  } 
} ] but actual calls were [ { 
  currentCareMoment : { 
    ID : 6, 
    Description : 'Avond (20-24)', 
    StartHour : 20, 
    EndHour : 24 
  }, 
  previousCareMoment : { 
    ID : 5, 
    Description : 'Late namiddag (16-20)', 
    StartHour : 16, 
    EndHour : 20 
  } 
} ].

【问题讨论】:

标签: angularjs jasmine


【解决方案1】:

我的回答是基于 jasmine 2.0.1。

方法 1 记录在 jasmine docs 中。所以它可能是推荐的。
但是方法 2 要简单得多。

方法一:使用自定义匹配器

我最初的想法是创建一个自定义匹配器,如 here 所述。所以我从 jasmine 源代码中复制了 toHaveBeenCalledWith 匹配器并对其进行了修改,以便它可以很好地打印:

var matchers = {
  toBeCalledWith: function (util, customEqualityTesters) {
    return {
      compare: function() {
        var args = Array.prototype.slice.call(arguments, 0),
          actual = args[0],
          expectedArgs = args.slice(1),
          result = { pass: false };

        if (!jasmine.isSpy(actual)) {
          throw new Error('Expected a spy, but got ' + jasmine.JSON.stringify(actual, undefined, 2) + '.');
        }

        if (!actual.calls.any()) {
          result.message = function() {
            return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + JSON.stringify(expectedArgs, undefined, 2) + ' but it was never called.';
          };
          return result;
        }

        if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) {
          result.pass = true;
          result.message = function() {
            return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + JSON.stringify(expectedArgs, undefined, 2) + ' but it was.';
          };
        } else {
          result.message = function() {
            return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + JSON.stringify(expectedArgs, undefined, 2) + ' but actual calls were ' + JSON.stringify(actual.calls.allArgs(), undefined, 2) + '.';
          };
        }

        return result;
      }
    };
  }
};

然后测试用例将使用我们的新匹配器:

describe('Test', function() {

  beforeEach(function() {
    jasmine.addMatchers(matchers);
  });

  it('should print pretty', function() {
    var spy = jasmine.createSpy('spy');
    spy({
      currentCareMoment: {
      ID: 5,
      Description: 'Late namiddag (16-20)',
      StartHour: 16,
      EndHour: 20
    },
    previousCareMoment: {
      ID: 4,
      Description: 'Namiddag (14-16)',
      StartHour: 14,
      EndHour: 16
    }});

    expect(spy).toBeCalledWith({
      currentCareMoment: {
        ID: 6,
        Description: 'Avond (20-24)',
        StartHour: 20,
        EndHour: 24
      },
      previousCareMoment: {
        ID: 5,
        Description: 'Late namiddag (16-20)',
        StartHour: 16,
        EndHour: 20
      }
    });
  });
});

方法二:覆盖jasmine.pp

然而,在实现这个过程中,我注意到 jasmine 使用函数 jasmine.pp 进行漂亮的打印。所以我想我可以通过在我的测试文件顶部添加以下内容来覆盖它:

jasmine.pp = function(obj) {
  return JSON.stringify(obj, undefined, 2);
};

【讨论】:

  • 非常好,Remco。我最终把它放在我的测试文件夹中的一个单独的 settings.js 文件中。然后我将它包含在 karma.conf.js 中文件数组的顶部,以便它对所有内容都可用。
【解决方案2】:

自从在此处添加其他答案以来,karma-jasmine-diff-reporter 中提供了一个漂亮的打印选项。我建议尝试一下——它非常可配置,并且与其他常见的测试报告器结合使用对我来说效果很好。

最小配置如下:

    reporters: ['jasmine-diff'],

    jasmineDiffReporter: {
        multiline: true,
        pretty: true
    },

【讨论】:

  • 我想如果 'jasmine-diff' 存在,则数组中不应出现“点”。您当前的示例输出报告两次。
【解决方案3】:

我发现覆盖 jasmine.pp 导致我的规范报告者不再对实际与预期的差异进行颜色编码。

我的解决方案是将以下 sn-p 添加到它自己的文件中,将其加载到 karma.conf,然后将自定义匹配器(使用 underscore 断言深度相等)添加到产生颜色的报告器的配置中-控制台中的编码差异 (karma-jasmine-diff-reporter)

//This will run before all of our specs because it's outside of a describe block
beforeEach(function() {
  var objectMatcher = {
    toEqualObject: function(util, customEqualityTesters) {
      return {
        compare: function(actual, expected) {
          var result = {};
          result.pass = _.isEqual(actual, expected);
          if (result.pass) {
            result.message = "Expected \n" + JSON.stringify(actual, null, 2) + "\n not to equal \n" + JSON.stringify(expected, null, 2) + "\n";
          } else {
            result.message = "Expected \n" + JSON.stringify(actual, null, 2) + "\n to equal \n" + JSON.stringify(expected, null, 2) + "";
          }
          return result;
        }
      };
    }
  };
  jasmine.addMatchers(objectMatcher);
});

现在我可以通过调用expect(foo).toEqualObject(bar) 在控制台中获得这样的输出:

弄清楚如何使用 jasmine spies 进行这项工作留给读者作为练习。

【讨论】:

  • 这实际上是一个好主意,我会看看我是否可以在记者中实现漂亮的打印。谢谢!
【解决方案4】:

使用

JSON.stringify(obj, undefined, 2)

第三个参数是缩进级别

【讨论】:

    【解决方案5】:

    这是一个 jasmine 自定义匹配器,它从匹配“预期”中的 Any 项的结果中删除调用参数。 Node 的util.inspect 用于处理循环引用和漂亮的打印结果。

    输出:

      custom matcher
        1) should not display arguments corresponding to expected Any arguments
    
      0 passing (15ms)
      1 failing
    
      1) custom matcher
           should not display arguments corresponding to expected Any arguments:
         ExpectationFailed: Expected spy underTest to have been called with [
      Any { expectedObject: [Function: Object] },
      { name: 'Bob' },
      { name: 'New York' },
      Any { expectedObject: [Function: Object] }
    ] but actual calls were [
      [
        '<Hidden due to Any match>',
        { name: 'Joe' },
        { name: 'New York' },
        '<Hidden due to Any match>'
      ]
    ].
    

    toHaveBeenCalledWith.js

    'use strict'
    
    import { inspect } from 'util'
    
    export function toHaveBeenCalledWith2 (util, customEqualityTesters) {
      return {
        compare: function () {
          const args = Array.prototype.slice.call(arguments, 0)
          const actual = args[0]
          const expectedArgs = args.slice(1)
          const result = { pass: false }
    
          if (!isSpyLike(actual)) {
            throw new Error('Expected a spy, but got ' + pp(actual) + '.')
          }
    
          if (!actual.calls.any()) {
            result.message = function () { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + pp(expectedArgs) + ' but it was never called.'; }
            return result
          }
    
          if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) {
            result.pass = true
            result.message = function () { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + pp(expectedArgs) + ' but it was.' }
          } else {
            const anyIndexes = getIndexesOfJasmineAnyArgs(expectedArgs)
            const actualArgs = stripJasmineAnyArgsFromActual(actual.calls.allArgs(), anyIndexes)
            result.message = function () { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + pp(expectedArgs) + ' but actual calls were ' + pp(actualArgs) + '.' }
          }
    
          return result
        },
      }
    }
    
    function stripJasmineAnyArgsFromActual (actualArgsList, indexesToIgnore) {
      const strippedArgs = []
      actualArgsList.forEach(args => {
        const stripped = args.map((arg, argIndex) => {
          if (indexesToIgnore.includes(argIndex)) {
            return '<Hidden due to Any match>'
          } else {
            return arg
          }
        })
        strippedArgs.push(stripped)
      })
      return strippedArgs
    }
    
    function getIndexesOfJasmineAnyArgs (expectedArgs) {
      const anyIndexes = []
      expectedArgs.forEach((arg, i) => {
        if (arg.constructor.name === 'Any') {
          anyIndexes.push(i)
        }
      })
      return anyIndexes
    }
    
    function isSpyLike (possibleSpy) {
      if (!possibleSpy) {
        return false
      }
      return possibleSpy.and && possibleSpy.and.constructor.name === 'SpyStrategy' &&
        possibleSpy.calls && possibleSpy.calls.constructor.name === 'CallTracker'
    }
    
    function pp (args) {
      return inspect(args, { depth: 5 })
    }
    

    用法:

    import { toHaveBeenCalledWith2 } from '../matchers/toHaveBeenCalledWith'
    
    describe('custom matcher', function () {
    
      beforeEach(async function () {
        const matchers = {
          toHaveBeenCalledWith2,
        }
        jasmine.addMatchers(matchers)
      })
    
      it('should not display arguments corresponding to expected Any arguments', function () {
        const obj = {
          underTest: function (a, person, city, d) { },
        }
        const expectedPerson = {
          name: 'Bob',
        }
        const expectedCity = {
          name: 'New York',
        }
    
        spyOn(obj, 'underTest')
    
        obj.underTest({}, { name: 'Joe' }, { name: 'New York' }, {})
    
        expect(obj.underTest).toHaveBeenCalledWith2(
          jasmine.any(Object),
          expectedPerson,
          expectedCity,
          jasmine.any(Object),
        )
      })
    })
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-04-26
      • 2011-09-12
      • 2021-06-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-07
      • 2014-05-19
      相关资源
      最近更新 更多