业力/茉莉花:漂亮的印刷对象比较

时间:2014-05-15 11:06:22

标签: angularjs jasmine

我目前正在使用Karma测试运行器来进行我的Angular项目,使用茉莉花测试框架。它工作得很好,但我有一个问题:当一个对象比较失败时,生成的打印到控制台真的很难读,并且这些对象拥有的属性越来越难。例如:

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 
  } 
} ].

5 个答案:

答案 0 :(得分:13)

我的回答是基于茉莉花2.0.1。

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

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

我最初的目的是创建一个自定义匹配器,如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
      }
    });
  });
});

方法2:覆盖jasmine.pp

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

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

答案 1 :(得分:7)

由于添加了其他答案的时间,karma-jasmine-diff-reporter中提供了漂亮的打印选项。我建议尝试一下 - 它非常易于配置,并且与其他常见的测试记者一起为我工作很好。

最小配置如下:

    reporters: ['jasmine-diff'],

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

答案 2 :(得分:5)

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

我的解决方案是将以下代码段添加到它自己的文件中,将其加载到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)

在控制台中获得这样的输出

Pretty console output

弄清楚如何使用茉莉花间谍进行这项工作留给读者练习。

答案 3 :(得分:0)

使用

JSON.stringify(obj, undefined, 2)

第3个参数是缩进级别

答案 4 :(得分:0)

这是一个茉莉花自定义匹配器,它从与“ expected”中的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),
    )
  })
})