将Sinon.fakeServer与promise和mocha结合使用

时间:2018-11-27 11:42:38

标签: node.js typescript mocha sinon

我的问题如下:我想测试一种将大量数据上传到AWS S3存储桶的方法。问题是:我不想每次测试时都真正上传数据,也不想关心环境中的凭证。因此,我想设置Sinon的假服务器模块来模拟上传并返回与S3相同的结果。可悲的是,使用async / await来查找包含代码的工作示例似乎很困难。

我的测试如下:

import {skip, test, suite} from "mocha-typescript";
import Chai from "chai";
import {S3Uploader} from "./s3-uploader.class";
import Sinon from "sinon";

@suite
class S3UploaderTest {

    public server : Sinon.SinonFakeServer | undefined;

    before() {
        this.server = Sinon.fakeServer.create();
    }

    after() {
        if (this.server != null) this.server.restore();
    }

    @test
    async "should upload a file to s3 correctly"(){

        let spy = Sinon.spy();

        const uploader : S3Uploader = new S3Uploader();
        const upload = await uploader.send("HalloWelt").toBucket("onetimeupload.test").toFolder("test/hw.txt").upload();

        Chai.expect(upload).to.be.a("object");
    }

}

在uploader.upload()方法内部,我从回调中解析了一个Promise。那么如何模拟上传过程?

编辑:这是s3-uploader的代码:

import AWS from "aws-sdk";

export class S3Uploader {
    private s3 = new AWS.S3({ accessKeyId : process.env.ACCESS_KEY_ID,  secretAccessKey : process.env.SECRET_ACCESS_KEY });
    private params = {
        Body: null || Object,
        Bucket: "",
        Key: ""
    };

    public send(stream : any) {
        this.params.Body = stream;
        return this;
    }

    public toBucket(bucket : string) {
        this.params.Bucket = bucket;
        return this;
    }

    public toFolder(path : string) {
        this.params.Key = path;
        return this;
    }

    public upload() {
        return new Promise((resolve, reject) => {

            if (process.env.ACCESS_KEY_ID == null || process.env.SECRET_ACCESS_KEY == null) {
                return reject("ERR_NO_AWS_CREDENTIALS");
            }

            this.s3.upload(this.params, (error : any, data : any) => {
                return error ? reject(error) : resolve(data);
            });
        });
    }
}

1 个答案:

答案 0 :(得分:0)

Sinon伪造服务器可以用来开发本身会发出请求的客户端,而不是像您所做的那样围绕AWS.S3这样的现有客户端进行包装。在这种情况下,最好不要设置AWS.S3的行为,而要测试它发出的实际请求。这样一来,您就可以避免测试AWS.S3的实现细节。

由于您使用的是TypeScript,并且已经制作了s3客户端private,因此需要进行一些更改以将其公开给测试。否则,如果TS编译器没有抱怨它,您将无法对它的方法进行存根。由于类似的原因,您也将无法使用params对象写断言。

由于我不经常使用TS,因此我对它的常见依赖项注入技术不太熟悉,但是您可以做的一件事是在S3Uploader类中添加可选的构造函数参数,以覆盖默认的{ {1}}和s3属性,如下所示:

arguments

之后,您可以创建一个存根实例,并将其传递给测试实例,如下所示:

constructor(s3, params) {
    if (s3) this.s3 = s3;
    if (params) this.params = params;
}

一旦有了存根实例,就可以编写assertions来确保const s3 = sinon.createStubInstance(AWS.S3); const params = { foo: 'bar' }; const uploader = new S3Uploader(s3, params); 方法以您希望的方式被调用:

upload

您还可以使用sinon stub api影响sinon.assert.calledOnce(s3.upload); sinon.assert.calledWith(s3.upload, sinon.match.same(params), sinon.match.func); 方法的行为。例如,使其失败,如下所示:

upload

或使其成功,例如:

s3.upload.callsArgWith(1, null);

您可能需要对每种情况使用一个完全独立的测试,使用实例const data = { whatever: 'data', you: 'want' }; s3.upload.callsArgWith(1, null, data); 挂钩以避免重复通用的设置。测试成功的方法将简单地before兑现承诺并检查其结果是否为数据。测试失败将涉及await,以确保承诺因适当的错误而被拒绝。

此外,由于您似乎在这里进行实际的单元测试,因此我建议分别测试每个S3Uploader方法 ,而不是一次进行大型测试就调用它们。这大大减少了您需要处理的可能案例的数量,从而使测试更加简单。像这样:

try/catch

如果我使用适当的Mocha进行此操作,则将每种方法的测试分组到一个嵌套的@suite class S3UploaderTest { params: any; // Not sure the best way to type this. s3: any; // Same. Sorry, not too experienced with TS. uploader: S3Uploader | undefined; before() { this.params = {}; this.s3 = sinon.createStubInstance(AWS.S3); this.uploader = new S3Uploader(this.s3, this.params); } @test "send should set Body param and return instance"() { const stream = "HalloWelt"; const result = this.uploader.send(stream); Chai.expect(this.params.Body).to.equal(stream); Chai.expect(result).to.equal(this.uploader); } @test "toBucket should set Bucket param and return instance"() { const bucket = "onetimeupload.test" const result = this.uploader.toBucket(bucket); Chai.expect(this.params.Bucket).to.equal(bucket); Chai.expect(result).to.equal(this.uploader); } @test "toFolder should set Key param and return instance"() { const path = "onetimeupload.test" const result = this.uploader.toFolder(path); Chai.expect(this.params.Key).to.equal(path); Chai.expect(result).to.equal(this.uploader); } @test "upload should attempt upload to s3"() { this.uploader.upload(); sinon.assert.calledOnce(this.s3.upload); sinon.assert.calledWith( this.s3.upload, sinon.match.same(this.params), sinon.match.func ); } @test async "upload should resolve with response if successful"() { const data = { foo: 'bar' }; s3.upload.callsArgWith(1, null, data); const result = await this.uploader.upload(); Chai.expect(result).to.equal(data); } @test async "upload should reject with error if not"() { const error = new Error('Test Error'); s3.upload.callsArgWith(1, error, null); try { await this.uploader.upload(); throw new Error('Promise should have rejected.'); } catch(err) { Chai.expect(err).to.equal(err); } } } 块中。我不确定describe是否会鼓励或什至可以,但是如果是这样,您可以考虑一下。