如何在单元测试中处理嵌套异步操作

时间:2014-05-11 14:16:07

标签: javascript unit-testing asynchronous promise

我有一个Javascript模块,它从另一个模块访问Promise对象,然后转换为它自己使用。我正在使用Bluebird库来确保所有的promise处理程序都是异步调用的。这对测试来说非常严重,特别是当内部承诺没有暴露时。

module.exports = (testedModule, app) ->
    app.module('otherModule').somePromise.then transformValue

transformValue = (val) ->
    return new Extra.TransformedValue(val)

在测试中,我正在嘲笑第一个承诺,所以我可以访问它。第二个承诺保留在模块内部,我不想仅仅为了测试而暴露它。请注意,我使用的是Mocha + Chai + Sinon。

beforeEach ->
    @initModule = -> app.module('testedModule', testedModule)  # prepare function to initialize tested module
    @dfd = dfd = Promise.defer() # defer promise resolution to tests
    app.module 'otherModule', (otherModule) ->
        otherModule.somePromise = dfd.promise

    @transformSpy = sinon.spy Extra, 'TransformedValue'  # spy on the constructor function
    @promiseTransform = dfd.promise.then =>
        # this usually fails as the spy is called more then once due to async nature of the tests
        @transformSpy.should.have.been.calledOnce
        # promise gets resolved with the return value of the spy
        # thus it should contain created instance of the TransformedValue
        return @transformSpy.firstCall.returnValue

afterEach ->
    @transformSpy.restore()

每次测试的一些准备工作。只需promiseTransform即可在每个测试中单独使用dfd.resolve()解析。但是transformSpy本身附加到全局对象,该对象由所有测试共享(也许应该存根)。大多数测试看起来像这样:

it 'should test whatever...', ->
    @init() # initialize module
    # something else is tested here, doesn't matter
    # note that @dfd is not resolved here, thus transformValue is not called yet

这样做很好,但接下来的测试实际上解决了dfd,这里的一切都变得混乱了。有时间谍不止一次解决或根本不解决。这是非常混乱的异步操作。

it 'should instantiate TransformedValue with correct argument', (done) ->
    expected = {foo: "bar"}
    @promiseTransform.then (transformed) =>
        # test here that TransformedValue constructor has been called once
        # ... it actually FAILS, because it was called twice already!
        @transformSpy.withArgs(expected).should.have.been.calledOnce
        transformed.should.be.an.instanceof Extra.TransformedValue
    # somehow this resolves promise for previous test and 
    # it causes `transformValue` to be called back there
    @dfd.resolve expected 
    @init()

我在这上花了2天时间,这已经让我疯了。测试应该是一个工具和要创建的实际代码。我可能错过了一些明显的解决方案。

您是否有任何一般(或具体)提示如何以更少的混淆和更多的控制和决定性来处理这个问题?目前我正在考虑将整个Promise存根以实际使其同步。但在我看来,它会使测试无效,因为工作流程可能与实际运行时不同。

1 个答案:

答案 0 :(得分:1)

与间谍有什么关系?如果这是同步代码,你就不会使用间谍。如果一切都是同步的,你会怎么写测试?

为什么不把测试写成:

it('should instantiate TransformedValue with correct argument', function() {
    var expected = {};
    return transform(expected).then(function(val) {
        assert.deepEqual(Extra.TransformedValue.value, expected)
        assert(val instanceof Extra.TransformedValue);
    });
});