简体   繁体   English

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

[英]Mixing sync and async tests using Mocha

I have a function, which computes some stuff, notifying the user via callbacks about some events: 我有一个函数,它计算一些东西,通过回调来通知用户一些事件:

function returnAndCallback(callback) {
  callback(5);  // not always invoked
  return 3;
}

Using Mocha and Should.js, I wrote this test: 使用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);
  });
});

This succeeds because the test ends when done() is called. 这成功是因为测试在调用done()done() It seems, I can either write a synchronous test and check the return value, or I can write an asynchronous test and check the callback. 看来,我可以编写同步测试并检查返回值,或者我可以编写异步测试并检查回调。

One can not use a sync test like this: 一个人不能像这样使用同步测试:

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);
  });
});

... because I want to assert that the callback is called. ...因为我想声明回调被调用。 This test would succeed, if the callback is never called. 如果从不调用回调,则此测试将成功。

How can I test both that the callback is called with the right value AND the correct value is returned? 我如何能够同时测试回调调用与正确的价值返回正确的值?

Duplicating tests is the only option I see. 复制测试是我看到的唯一选择。

Edit: I notice, that I use the term asynchron wrongly here. 编辑:我注意到,我在这里错误地使用了术语asynchron。 The callbacks are merely a way to inject optional actions into a function, which transforms an object. 回调只是将可选动作注入函数的一种方法,该函数转换对象。 All code is synchronous, but the control flow sometimes branches of into callbacks and I want to be able to recognize that. 所有代码都是同步的,但控制流有时会分支到回调中,我希望能够识别出来。

Here is another possible way to test that: 这是另一种测试方法:

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);
  });
});

But still not perfectly elegant due to the extra variable. 但由于额外的变量,仍然不是完美的优雅。

First, be aware that done() implies a synchronous test; 首先,要注意done()意味着同步测试; Mocha's default is to run tests asynchronously . Mocha的默认设置是异步运行测试。 If you want to test the 'returned' value from asynchronous functions (functions that return a value in a callback function), you run them synchronously, via done() . 如果要测试异步函数(在回调函数中返回值的函数)中的“返回”值,可以通过done()同步运行它们。

Next, you can't return a value from an asynchronous function. 接下来,您无法从异步函数返回值。 These two behaviours are mutually exclusive: 这两种行为是互斥的:

function returnAndCallback(callback) {
  callback(5);  // not always invoked
  return 3;
}

You want to only execute the callback. 你想执行回调。

It appears to me that you're expecting that sometimes a callback is passed, but not always. 在我看来,你期望有时回传,但并非总是如此。 In that case, I'd separate the function tests (I think you'll need to use done() everywhere, to persist the synchronous nature of the tests) and do a check for callback inside the function itself. 在那种情况下,我将功能测试分开(我认为你需要在任何地方使用done() ,以保持测试的同步性)并检查函数本身内部的回调。

Now that we've got that clarified, since you want to assert that a callback is called, we need to establish some baseline assumptions: 现在我们已经澄清了,因为你想断言调用了一个回调,我们需要建立一些基线假设:

  • A) A callback should be a function A)回调应该是一个函数
  • B) A callback should be called with a parameter that contains a value B)应使用包含值的参数调用回调

You want to test for both of these things. 你想测试这两件事。 A) is easy to prove: you're writing the callback function as part of your test, so if you passed say, null or undefined , of course the test will fail, but that's not the point of this test. A)很容易证明:你正在编写回调函数作为测试的一部分,所以如果你传递了say, nullundefined ,当然测试会失败,但这不是这个测试的重点。 Here is how you prove both A) and B): 以下是您如何证明A)和B):

function optionalAsync(callback) {
  if (typeof callback === 'function') {
    callback(4)
  } else {
    return 3
  }
}

describe('optional async function', function() {
  it('should return 4 when a callback is passed', function(done) {
    optionalAsync(function(value) {
        should(value).be.eql(4)
        done()
    })
  })
  it('should return 3 when no callback is passed', function(done) {
    optionalAsync().should.be.eql(3)
    done()
  })
})

This is kind of strange, but given your use case, it does make sense to check for both possibilities. 这有点奇怪,但考虑到你的用例,检查这两种可能性是有意义的。 I'm sure you could reduce the code footprint a bit too, but I'd suggest keeping it this way for the sake of readability for when you shelve tis for a year and forget what you did ;) 我确信你可以减少代码占用空间,但我建议保持这种方式,以便在你搁置一年并忘记你所做的事情时的可读性;)

Now after all of this if you still want to have the option for a function to run synchronously you can do so by blocking the event loop: https://stackoverflow.com/a/22334347/1214800 . 现在,如果您希望同时运行某个函数的选项,则可以通过阻止事件循环来执行此操作: https//stackoverflow.com/a/22334347/1214800

But why would you want to? 但你为什么要这样?

Save yourself the trouble of handling synchronous operations in an inherently non-blocking, asynchronous platform, and write everything (even the non-IO-blocking operations) with a callback: 避免在本质上非阻塞的异步平台中处理同步操作,并使用回调编写所有内容(甚至是非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()
    })
  })
})

Here is your answer written with everything as callback (even the short ops): 以下是您回答的所有内容(即使是短操作):

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)
    })
  })
})

Here is another solution for this problem. 这是此问题的另一种解决方案。 It just adds an additional line of code in tests which want to ensure callback execution. 它只是在测试中添加了一行代码,以确保回调执行。

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);
  });
});

Please also note sinon which does something similar. 请注意sinon做类似的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM