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

时间:2017-01-30 18:42:31

标签: javascript node.js unit-testing mocha

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

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

...因为我想声明回调被调用。如果从不调用回调函数,则此测试将成功。

如何测试使用正确值 AND 调用回调?返回正确的值?

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

编辑:我注意到,我在这里错误地使用了术语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);
  });
});

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

2 个答案:

答案 0 :(得分:2)

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

接下来,您无法从异步函数返回值。这两种行为是互斥的:

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

您希望执行回调。

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

现在我们已经澄清了,因为你想断言调用了一个回调,我们需要建立一些基线假设:

  • A)回调应该是一个功能
  • B)应使用包含值
  • 的参数调用回调

您想要测试这两件事。 A)很容易证明:你正在编写回调函数作为测试的一部分,所以如果你通过说nullundefined,当然测试会失败,但这不是重点这个测试。以下是你如何证明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()
  })
})

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

现在,如果仍然想让某个函数同步运行,那么可以通过阻止事件循环https://stackoverflow.com/a/22334347/1214800来实现。

但你为什么要这样做?

避免在本质上非阻塞的异步平台中处理同步操作的麻烦,并使用回调编写所有内容(甚至是非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()
    })
  })
})

以下是您的回答,其中包含所有内容作为回调(即使是短操作):

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

答案 1 :(得分:1)

这是此问题的另一种解决方案。它只是在测试中增加了一行代码,以确保回调执行。

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

请注意sinon做类似的事情。