【问题标题】:Nodejs - testing AWS with MochaNodejs - 使用 Mocha 测试 AWS
【发布时间】:2015-03-05 15:25:41
【问题描述】:

我在为以下使用AWSgraphicsmagicknodejs 代码编写测试时遇到问题。我还尝试搜索有关如何为asyncwaterfall 方法编写测试的示例,但没有任何明确的结果。

// dependencies
var async = require('async');
var AWS = require('aws-sdk');
var gm = require('gm').subClass({ imageMagick: true });
var util = require('util');

// get reference to S3 client
var s3 = new AWS.S3();

exports.AwsHandler = function(event, context) {
// Read options from the event.
console.log("Reading options from event:\n", util.inspect(event, {depth: 5}));
var srcBucket = event.Records[0].s3.bucket.name;
var srcKey = event.Records[0].s3.object.key;
var dstnKey = srcKey;


// Infer the image type.
var typeMatch = srcKey.match(/\.([^.]*)$/);
if (!typeMatch) {
    console.error('unable to infer image type for key ' + srcKey);
    return;
}

var imageType = typeMatch[1];

if (imageType != "jpg" && imageType != "png") {
    console.log('skipping non-image ' + srcKey);
    return;
}

//Download the image from S3, transform, and upload to same S3 bucket but different folders.
async.waterfall([
        function download(next) {
            // Download the image from S3 into a buffer.

            s3.getObject({
                    Bucket: srcBucket,
                    Key: srcKey
                },
                next);
        },

        function transformSave(response, next) {

            var _buffer = null;

            for (var i = 0; i<len; i++) {

                // Transform the image buffer in memory.
                gm(response.Body, srcKey)
                    .resize(_sizesArray[i].width)
                    .toBuffer(imageType, function(err, buffer) {
                        if (err) {
                            next(err);
                        } else {
                            console.log(buffer);
                            _buffer = buffer;
                        }
                    });

                // put newly resized image into respective folder
                s3.putObject({
                    Bucket: srcBucket,
                    Key: "dst/" + _sizesArray[i].destinationPath + "/" + dstnKey,
                    Body: _buffer,
                    ContentType: response.ContentType
                }, next);
            }
        },

    ], function (err) {
        if (err) {
            console.error(
                '---->Unable to resize ' + srcBucket + '/' + srcKey +
                ' and upload to ' + srcBucket + '/dst' +
                ' due to an error: ' + err
            );
        } else {
            console.log(
                '---->Successfully resized ' + srcBucket +
                ' and uploaded to ' + srcBucket + "/dst"
            );
        }

        context.done();
    }
);

}; 到目前为止我对该模块的测试:

require('blanket')({
    pattern: function (filename) {
        return !/node_modules/.test(filename);
    }
});

// in terminal, type the following command to get code coverage: mocha -R html-cov > coverage.html

var chai = require('chai');
var sinonChai = require("sinon-chai");
var expect = chai.expect;
var sinon = require('sinon');
chai.use(sinonChai);
var sync = require("async");

var proxyquire = require('proxyquire');



describe('Image Resizing module', function () {
    var gmSubclassStub = sinon.stub();
    var getObjectStub = sinon.stub();
    var putObjectSpy = sinon.spy();

    var testedModule = proxyquire('../index', {
        'gm': {subClass: sinon.stub().returns(gmSubclassStub)},
        'AWS': {
            "s3": {
                getObject: sinon.stub().returns(getObjectStub),
                putObject: putObjectSpy
            }
        }
    });

   describe('AwsHandler', function () {

        var event = {
            "Records": [
            {
                "s3": {
                    "bucket": {
                        "name": "testbucket"
                    },
                    "object": {
                        "key": "test.jpg"
                    }
                }
            }
        ]
        };


        it("should call gm write with correct files", function () {
            // Arrange


            // Spies are the methods you expect were actually called
            var buffer800Spy = sinon.spy();
            var buffer500Spy = sinon.spy();
            var buffer200Spy = sinon.spy();
            var buffer45Spy = sinon.spy();

            // This is a stub that will return the correct spy for each iteration of the for loop
            var resizeStub = sinon.stub();
            resizeStub.withArgs(800).returns({toBuffer: buffer800Spy});
            resizeStub.withArgs(500).returns({toBuffer: buffer500Spy});
            resizeStub.withArgs(200).returns({toBuffer: buffer200Spy});
            resizeStub.withArgs(45).returns({toBuffer: buffer45Spy});


            // Stub is used when you just want to simulate a returned value
            var nameStub = sinon.stub().yields({"name": "testbucket"});
            var keyStub = sinon.stub().yields({"key": "test.jpg"});
            gmSubclassStub.withArgs(event).returns({resize:resizeStub});
            getObjectStub.withArgs(event).yields({name: nameStub}, {key: keyStub});

            // Act - this calls the tested method
            testedModule.AwsHandler(event);

            // Assert

        });
    });
});

【问题讨论】:

  • 嗨。你成功了吗?答案有帮助吗?
  • 嗨。我还没试过。我一直在关注另一个问题。我会在今晚或明天晚些时候试一试。谢谢;)

标签: node.js amazon-web-services mocha.js


【解决方案1】:

在这里很难回答这种问题;这个问题不是很具体,也不是一个开放的问题,可以用意见、想法等来回答。

因此,我创建了一个类似的实现来解决 async.waterfall 问题并提供一个测试以 100% 的覆盖率测试 AwsHandler

代码在这个gist,因为在那里比这里更方便和可读。

我还写了一个与这个实现相关的blog post

【讨论】:

    【解决方案2】:

    有几点需要改变:

    • 您想测试单元的操作,而不测试实现。这就是为什么您应该忽略测试中的异步(就像您所做的那样)。 它只是实现方法的一种方式,即单元的内部工作方式。 您应该测试的是,在给定条件下,该单元给出了预期的最终结果,在这种情况下,它调用了 s3.putObject。 因此,您应该 stub 外部的所有内容(gm 和 aws),以及 s3.putObject 方法上的 spy,因为这是预期的最终结果。

    • 在您的存根中,您使用了“yield”,它调用回调函数,但前提是它是第一个参数。 如果不是,就像我们的例子一样,你需要使用“callsArgWith(index,...)”和回调参数的索引。

    • proxyquire 的注入模块必须与需要它们的代码中的名称完全相同 - 将“AWS”更改为“aws-sdk” 检查存根是否正确注入的一种方法是在调试器中,请注意 “s3”变量,并检查它是“function proxy()”而不是“function()”。如果您不使用调试器,也可以将其打印到控制台。

    • 您的模块正在 for 循环中调用 next,这会导致瀑布拆分为一棵树,其中包含 36 次 done(!) 调用。 也许您应该使用不同的异步模型,例如 map reduce。我通过添加一个愚蠢的条件来修复它,但这不是好的代码。

    • 附带说明,您可以看到测试变得非常复杂。 这可能表明测试的代码可以使用一些关注点分离。 例如,将 gm 操作和 s3 操作移到两个单独的模块中可以帮助分离事物,也可以更轻松地进行测试。

    模块本身的变化,防止调用next 4*4次:

    function transform(response, next) {
    
            for (var i = 0; i<len; i++) {
    
              // Transform the image buffer in memory.
              gm(response.Body, srcKey)
                .resize(_sizesArray[i].width)
                .toBuffer(imageType, function(err, buffer) {
                  if (err) {
                    next(err);
    
                  } else {
                    next(null, response.ContentType, buffer, i);
                  }
                });
            }
          },
    
          function upload(contentType, data, i, next) {
    
              // Stream the transformed image to a different folder.
              s3.putObject({
                  Bucket: srcBucket,
                  Key: "dst/" + _sizesArray[i].destinationPath + "/" + dstnKey,
                  Body: data,
                  ContentType: contentType
                },
                function(err) {
                  if (i==3) next(err);
                });
          }
    

    还有测试:

    describe.only('Image Resizing module', function () {
        var gmSubclassStub = sinon.stub();
        var s3Stub = {};
    
        var proxyquire = require('proxyquire');
        var testedModule = proxyquire('../index', {
            'gm': {subClass: sinon.stub().returns(gmSubclassStub)},
            'aws-sdk': {"S3": sinon.stub().returns(s3Stub)}
        });
    
        describe('AwsHandler', function () {
    
            var event = {};
    
            // The done callback is used for async testing
            it("should call gm write with correct files", function (done) {
                // Arrange
                var resizeStub = sinon.stub();
                var buffer800Spy = sinon.stub().withArgs("jpg").callsArgWith(1, null, "800 buffer");
                var buffer500Spy = sinon.stub().withArgs("jpg").callsArgWith(1, null, "500 buffer");
                var buffer200Spy = sinon.stub().withArgs("jpg").callsArgWith(1, null, "200 buffer");
                var buffer45Spy = sinon.stub().withArgs("jpg").callsArgWith(1, null, "45 buffer");
                resizeStub.withArgs(800).returns({toBuffer: buffer800Spy});
                resizeStub.withArgs(500).returns({toBuffer: buffer500Spy});
                resizeStub.withArgs(200).returns({toBuffer: buffer200Spy});
                resizeStub.withArgs(45).returns({toBuffer: buffer45Spy});
    
                gmSubclassStub.withArgs("response body", "test.jpg").returns({resize: resizeStub});
    
                s3Stub.getObject = sinon.stub()
                    .withArgs({name: "testbucket", key: "test.jpg"})
                    .callsArgWith(1, null, {
                        Body: "response body",
                        ContentType: "response content type"
                    });
                var putObjectMock = sinon.mock();
                s3Stub.putObject = putObjectMock;
                putObjectMock.callsArgWith(1, null, {}); // return behaviour of the mock
                putObjectMock.exactly(4); // sets expectation that it is called 4 times
    
                // Act - this calls the tested method
                testedModule.AwsHandler(event, {
                    done: function () {
    
                        // Assertions need to be inside callback because it is async
                        assert.deepEqual(putObjectMock.getCall(0).args[0], {
                            Bucket: "testbucket",
                            Key: "dst/large/test.jpg",
                            Body: "800 buffer",
                            ContentType: "response content type"
                        });
                        assert.deepEqual(putObjectMock.getCall(1).args[0], {
                            Bucket: "testbucket",
                            Key: "dst/medium/test.jpg",
                            Body: "500 buffer",
                            ContentType: "response content type"
                        });
                        assert.deepEqual(putObjectMock.getCall(2).args[0], {
                            Bucket: "testbucket",
                            Key: "dst/small/test.jpg",
                            Body: "200 buffer",
                            ContentType: "response content type"
                        });
                        assert.deepEqual(putObjectMock.getCall(3).args[0], {
                            Bucket: "testbucket",
                            Key: "dst/thumbnail/test.jpg",
                            Body: "45 buffer",
                            ContentType: "response content type"
                        });
    
                        // This ends the async test
                        done();
                    }
                });
            });
        });
    });
    

    【讨论】:

    • 由于某种原因,我在调用testedModule.AwsHandler 的行中收到错误TypeError: Cannot read property '0' of undefined
    • 我在 proxyquire 行中留下了错误的文件名,在你原来的测试中是 '../index'。也许这就是问题?用 var testingModule = proxyquire('../index', { 编辑了答案
    • 我注意到了这一点并在收到此错误之前进行了更改。
    • 现在我记得我放了“var event = {}”而不是json的真正初始化,因为它太长了。将其替换为您自己的事件对象。
    • 我发现并改变了它。现在我收到以下错误:` Uncaught AssertionError: expected { Object (Bucket, Key, ...) } to deep equal { Object (Bucket, Key, ...) } + expected - actual { "Body": "500 缓冲区" "Bucket": "testbucket" "ContentType": "响应内容类型" + "Key": "dst/medium/test.jpg" - "Key": "dst/large/test.jpg" }`
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-08
    • 2013-12-10
    • 2021-07-24
    • 1970-01-01
    相关资源
    最近更新 更多