【问题标题】:How to use spies in testing factories with promises?如何在有承诺的测试工厂中使用间谍?
【发布时间】:2015-10-11 02:33:20
【问题描述】:

我想测试这段代码:

'use strict';

var Promise = require('bluebird');
var AWS = require('aws-sdk');
var fs = Promise.promisifyAll(require('fs-extra'));
var path = require('path');

var dispatcher = function() {
  return {
    /**
     * @param {Object} options
     * @param {string} options.s3_access_key
     * @param {string} options.s3_secret_key
     * @param {string} options.s3_bucket
     * @param {string} options.file - Path to the file to be uploaded
     * @param {string} [options.file_key] - Name to save the file under, defaults to the original file's name
     * @returns {Function} Promise - A promise resolved with `true` or rejected with an Error
     */
    upload: Promise.method(function upload(options) {
      var s3_options = {
        accessKeyId: options.s3_access_key,
        secretAccessKey: options.s3_secret_key,
        params: {
          Bucket: options.s3_bucket
        }
      };
      var file_stream = fs.createReadStream(options.file);
      var file_key = options.file_key || path.basename(options.file);
      var promisedS3 = Promise.promisifyAll(new AWS.S3(s3_options));

      return promisedS3.putObjectAsync({
        Body: file_stream,
        Key: file_key
      });
    })
  };
};

module.exports = {
  create: dispatcher
};

我知道如何进行集成测试,但我还想验证 promisedS3 是否使用正确的参数被调用。我认为我需要为此使用间谍,但我不确定如何将自己“插入”到来自测试的 promiseS3.putObjectAsync 以拦截参数并进行比较。

我想避免将对 S3 的调用包装在私有方法中并继续使用工厂。

【问题讨论】:

  • 你考虑过使用 sinon 吗?
  • 我有,但我不确定我是否完全理解如何从我的代码中利用 promisedS3 对象。我可以var spy = sinon.spy(require('./dispatcher'), 'upload');,但我看不出它如何帮助我监视promisedS3.putObjectAsync 电话。
  • 请在对象原型上调用promisifyAll,而不是在每个新实例上调用 - 它会明显更快。
  • @BenjaminGruenbaum,如果我这样做Promise.promisifyAll(AWS.S3),它将无法正常工作:TypeError: Cannot promisify an API that has normal methods with 'Async'-suffix See http://goo.gl/iWrZbw

标签: node.js testing promise mocha.js chai


【解决方案1】:

(在cmets之后编辑)

在您的测试中,您可以使用像 proxyquire 这样的库来存根/模拟外部依赖项。

我刚刚对此进行了测试,它可以工作:

'use strict';

var sinon = require( 'sinon' );
var proxyquire = require( 'proxyquire' );
var expect = require('must');

var awsStub = {
    S3: function(){
    },
    '@noCallThru': true
};

var fsStub = {
    createReadStream: function(){
        return {};
    },
    '@noCallThru': true
};

describe( 'S3 uploader', function(){
    var subject, spy;
    beforeEach( function(){
        spy = sinon.spy();
        awsStub.S3.prototype.putObject = function( params, callback ){
            spy(params);
            callback();
        };
        subject = proxyquire( '../lib/s3uploader', {
            'aws-sdk': awsStub,
            'fs-extra': fsStub
        } );

    } );
    it( 'should delegate to AWS S3 `putObject`', function( done ){
        subject().upload( {file:''} ).then( function(){
            expect( spy.callCount ).to.equal( 1 );
            done();
        } )
    } );
} );

lib/s3uploader 是您在上面发布的文件。

默认情况下,仍会调用存根依赖项的原始方法。如果您不希望这样,您需要将 @noCallThru: true 传递给 proxyquire 调用,如上所述。

【讨论】:

  • Promise.promisfyAll(new AWS.S3()); 使这不起作用。我认为我无法访问promisedS3,因为它不在模块的全局范围内。如果我模拟AWS.S3.putObject,当我从upload 中检查AWS.S3 对象时,我可以看到模拟,但是当我new AWS.S3() 然后承诺它时,putObject 方法不是模拟。
  • 啊,是的,我没有意识到这是一个“类”。正如 Benjamin Gruenbaum 所写,您必须承诺原型,而不是实例。我会更新我的答案。
  • 谢谢。为什么我应该承诺原型而不是实例?如果我在没有存根但使用 nock 来模拟 API 调用和对 S3 的响应的情况下运行您的代码,promisedS3.putObjectAsync 将失败,因为putObjectAsync 方法未定义。我认为它在您的示例中通过,因为您将它存根,因此它不存在于原始对象上,而是因为您在存根上创建它然后代码运行。
  • 通常最好对原型进行承诺,因为它只需要发生一次。但你说得对,在这种情况下它不再起作用了。因此,解决方案是像以前一样承诺实例。刚刚用模拟的 未模拟的对其进行了测试,它在两者中都有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-27
  • 2014-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多