Javascript 使用Mocha混合同步和异步测试

Javascript 使用Mocha混合同步和异步测试,javascript,node.js,unit-testing,mocha.js,Javascript,Node.js,Unit Testing,Mocha.js,我有一个函数,它计算一些东西,通过回调通知用户一些事件: function returnAndCallback(callback) { callback(5); // not always invoked return 3; } 使用Mocha和Should.js,我编写了以下测试: describe('mixing sync and async code', function() { it('should test callback AND return value', fun

我有一个函数,它计算一些东西,通过回调通知用户一些事件:

function returnAndCallback(callback) {
  callback(5);  // not always invoked
  return 3;
}
使用Mocha和Should.js,我编写了以下测试:

describe('mixing sync and async code', function() {
  it('should test callback AND return value', function(done) {
    returnAndCallback(function(value) {
      value.should.be.eql(5);
      done();
    }).should.be.eql(4);
  });
});
这会成功,因为当调用
done()
时,测试将结束。看起来,我可以编写一个同步测试并检查返回值,也可以编写一个异步测试并检查回调

不能使用这样的同步测试:

describe('mixing sync and async code', function() {
  it('should test callback AND return value', function() {
    returnAndCallback(function(value) {
      should.be.eql('difficult to get result');
    }).should.be.eql(3);
  });
});
。。。因为我想断言调用了回调。如果从未调用回调,则此测试将成功

如何测试调用回调时是否使用了正确的值,以及是否返回了正确的值

重复测试是我看到的唯一选择

编辑:我注意到,我在这里错误地使用了术语asynchron。回调仅仅是将可选操作注入到函数中的一种方式,该函数将转换对象。所有代码都是同步的,但控制流有时会分支到回调中,我希望能够识别这一点

下面是另一种可能的测试方法:

describe('mixing sync and async code', function() {
  it('should test callback AND return value', function() {
    let callbackValue;

    returnAndCallback(function(value) {
      callbackValue = value;
    }).should.be.eql(3);

    callbackValue.should.be.eql(5);
  });
});

但是由于额外的变量,仍然不完美。

首先,请注意
done()
意味着同步测试;Mocha的默认设置是异步运行测试。如果要测试异步函数(在回调函数中返回值的函数)的“返回”值,可以通过
done()
同步运行它们

接下来,不能从异步函数返回值。这两种行为是相互排斥的:

function returnAndCallback(callback) {
  callback(5);  // not always invoked
  return 3;
}
您只希望执行回调

在我看来,您希望有时会传递回调,但并非总是这样。在这种情况下,我将分离函数测试(我认为您需要在任何地方使用
done()
,以保持测试的同步性),并检查函数本身内部的回调

现在我们已经澄清了这一点,因为您希望断言调用了回调,所以我们需要建立一些基线假设:

  • A)回调应该是函数
  • B)应使用包含值的参数调用回调
您需要测试这两种情况。A) 很容易证明:您正在编写回调函数作为测试的一部分,因此如果您通过测试,比如说,
null
undefined
,测试当然会失败,但这不是本测试的重点。以下是您如何证明A)和B):

这有点奇怪,但是考虑到您的用例,检查这两种可能性是有意义的。我相信您也可以减少一点代码占用,但我建议您保持这种方式,以便在您将tis搁置一年而忘记您所做的事情时可读性;)

现在,在完成所有这些之后,如果您仍然希望有同步运行函数的选项,那么可以通过阻塞事件循环来实现

但你为什么要这么做

省去了在固有的非阻塞、异步平台中处理同步操作的麻烦,并使用回调写入所有内容(甚至是非IO阻塞操作):

function optionallyLongRunningOp(obj, callback) {
  if (typeof callback === 'function') {
    validateObject(obj, function(err, result) {
       // not always long-running; may invoke the long-running branch of your control-flow
       callback(err, result)
    })
  } else {
    throw new Error("You didn't pass a callback function!")
  }
}

describe('possibly long-running op async function', function() {
  it('should return 4 when control flow A is entered', function(done) {
    obj.useControlFlow = "A"
    validateObject(obj, function(err, result) {
        // this is a slow return
        should(result.value).be.eql(4)
        done()
    })
  it('should return 3 when control flow B is entered', function(done) {
    obj.useControlFlow = "B"
    validateObject(obj, function(err, result) {
        // this is a quick return
        should(result.value).be.eql(3)
        done()
    })
  })
})
以下是您的答案,所有内容都作为回调(即使是简短的操作):


这是这个问题的另一个解决方案。它只是在希望确保回调执行的测试中添加了一行额外的代码

let should = require('should');

function doubleAndNotifyEven(num, reportEven) {
  if (num % 2 == 0) {
    reportEven(num);
  }

  return 2 * num;
}

function mandatoryCallback(callback) {
  mandatoryCallback.numCalled = 0;
  return function () {
    mandatoryCallback.numCalled++;
    return callback.apply(this, arguments);
  };
}

describe('checking return value and callback execution', function() {
  it('should double and report given an even number', function() {
    doubleAndNotifyEven(2, mandatoryCallback(function(value) {
      should(value).be.eql(2);
    })).should.be.eql(4);

    should(mandatoryCallback.numCalled).greaterThan(0);
  });

  it('should double and not report anything given an odd number', function() {
    doubleAndNotifyEven(3, function(value) {
      throw new Error('Wrong report!');
    }).should.be.eql(6);
  });
});

还请注意哪些功能与此类似。

我并不是想让人觉得迂腐,但您的功能似乎是错误的。如果你有一个回调,那就是你返回的。为什么有时会有回调,有时会有返回?具体的用例是一个验证器/检查器,它获取一个对象,检查它,可能会更改一些字段。可以进行回调,该回调报告所有更改。最后返回checked对象。在实践中,如何使用这样的函数?我使用Socket.IO并从大量不同的客户端实现中获取事件消息对象。我希望在处理这些消息时尽可能地健壮。有些人只是缺少一个我可以用默认值填充的字段,但我仍然想通知客户机(用于调试),因此我发送了一个警告事件,该事件将在回调中发出。如果真的出了什么问题,客户端会收到一个错误事件(当我想知道为什么gps位置出错时,这会帮助我更快地发现输入错误。我给出了一个latutide,但Chrome没有提示)。同样,我可以返回一个对象
{result:…,warning:[…],errors:[…]}
,但是,如果我能做
msg.gps=validate(msg.gps,reportError,reportWarn)
,其中
report*
函数是可选的或可选的no-ops,那么它看起来就不那么优雅了。非常感谢您的努力!这不完全是我想要的。我想我把问题说错了(同步和异步都搞砸了)。您对异步返回的处理是正确的,我现在接受这个答案。我编辑了这个问题,可以将其重新表述为“如何使该变量消失?”…如果您的
值类似于
null
或未定义整个测试中断,请不要执行
value.should.be.eql(4)
。另外,为了获得完整的错误反馈,
尝试{expect(value).to.equal(4);done();}catch(e){done(e);}
好的,是的,但是由于函数显式地设置了值(它不可能为null或未定义),我没有检查稳定性;)讨论的重点是,我质疑你为什么要同步写作?它破坏了整个节点模型。您可以异步编写所有内容,希望始终存在回调,然后简单地测试回调的值。将验证器构建到模块中并将其导出-您可以利用函数式编程设计将深层对象属性检查映射到模块中,然后返回
var doLongRunnignOp = function(cb) {
    var didNotify = true
    cb(didNotify)
}

function doubleAndNotifyEven(num, cb) {
  if (num % 2 == 0) {
    doLongRunnignOp(function(didNotify) {
      cb(num)
      // did notify
    })
  } else {
    cb(2 * num)
    // instant return, did not notify
  }
}

describe('checking return value and callback execution', function() {
  it('should double and report given an even number', function() {
    doubleAndNotifyEven(2, function(value) {
      should(value).be.eql(2)
    })
  })

  it('should double and not report anything given an odd number', function() {
    doubleAndNotifyEven(3, function(value) {
      should(value).be.eql(6)
    })
  })
})
let should = require('should');

function doubleAndNotifyEven(num, reportEven) {
  if (num % 2 == 0) {
    reportEven(num);
  }

  return 2 * num;
}

function mandatoryCallback(callback) {
  mandatoryCallback.numCalled = 0;
  return function () {
    mandatoryCallback.numCalled++;
    return callback.apply(this, arguments);
  };
}

describe('checking return value and callback execution', function() {
  it('should double and report given an even number', function() {
    doubleAndNotifyEven(2, mandatoryCallback(function(value) {
      should(value).be.eql(2);
    })).should.be.eql(4);

    should(mandatoryCallback.numCalled).greaterThan(0);
  });

  it('should double and not report anything given an odd number', function() {
    doubleAndNotifyEven(3, function(value) {
      throw new Error('Wrong report!');
    }).should.be.eql(6);
  });
});