expect() 没有实际期望

expect() with no actual expectations

问题:

最近,在审查我们现有的测试代码库时,我注意到在没有 "matching" 部分的情况下使用 expect() 时会出现一种危险的 typo/mistake:

expect(page.filters.fromDateLabel.getText(), "After");

我很确定 toEqual() 是要在这里使用的:

expect(page.filters.fromDateLabel.getText()).toEqual("After");

问题是 jasmine 在这种情况下不会辜负预期(好吧,显然是因为实际上没有预期)。这让我们遇到了一个更严重的问题——在测试用例中实际上没有测试任何东西——它在没有期望的情况下通过了。我们对测试的内容产生了错误的认识。

问题:

我想尽快发现这些错误。你认为我应该如何处理这个问题?

想法:

我倾向于认为静态分析路线是最好的,但如果您正在寻找一种快速而肮脏的方法,这里有一些代码可以获取所有对 expect 的调用返回的期望并创建一个跟踪是否使用过任何期望属性的代理:

var unusedExpectations = new Set();

var originalExpect = window.expect;  // Should be empty after every spec
var expect = function() {
  var rawExpectation = originalExpect.apply(this, arguments);
  unusedExpectations.add(rawExpectation);  // Assume unused until used

  // Traverse expectation and its prototypes, copying all properties to
  // our proxy object. (Note that this becomes much simpler if you have
  // ES6 Proxy in your environment.)

  var proxy = {}
  for(var proto = rawExpectation; proto; proto = proto.__proto__) {
    Object.getOwnPropertyNames(proto).forEach(function(prop) {
      if(Object.getOwnPropertyDescriptor(proxy, prop))
        return;
      Object.defineProperty(
        proxy, prop, {
          get: function() {
            // Aha! Somebody used this expectation for _something_.
            unusedExpectations.delete(rawExpectation);
            return rawExpectation[prop];
          }
        }
      );
    });
  }
  return proxy;
}

把它放在你的规格中隐藏 Jasmine 的 expect 的地方,然后:

beforeEach(function() {
  unusedExpectations.clear();
});
afterEach(function() {
  expect(unusedExpectations.size).toEqual(0);
});

注意事项:

  1. 有点邪恶。
  2. 不会捕捉到 expect(foo).toBeFalsy;(缺少括号)。
  3. 计算any属性的使用,所以不会catchexpect(foo).toString().

仍然有效!

可以添加代码来检查堆栈跟踪并提取有问题的位置 expect(),但我认为标记哪个规范有未使用的 expect() 就足够了。

答案中提供的自定义 ESLint 规则现在是 eslint-plugin-jasmine 1.6.0:

的一部分

旧答案:

这是 custom ESLint rule 我最后得到的:

module.exports = function (context) {
  return {
    // checking "expect()" arguments
    CallExpression: function (node) {
      if (node.callee.name === 'expect') {
        if (node.arguments.length > 1) {
          context.report(node, 'More than one argument passed to expect()')
        } else if (node.arguments.length === 0) {
          context.report(node, 'No arguments passed to expect()')
        }
      }
    },

    // nothing called on "expect()"
    'CallExpression:exit': function (node) {
      if (node.callee.name === 'expect' && node.parent.type === 'ExpressionStatement') {
        context.report(node, 'Nothing called on expect()')
      }
    }
  }
}

它检查 3 件事:

  • 超过 1 个参数传递给 expect()
  • 没有参数传递给 expect()
  • expect()
  • 上没有任何调用

这是它当前捕获的无效 expect() 用法示例:

expect(page.filters.fromDateLabel.getText(), "After");
expect("After");
expect();

至于选项 #1,实际上有一个非常相关且有用的 ESLint 规则已经由 [eslint-plugin-jasmine] 实现并开源: