node.js - 使用带有 promises 和 mocha 的 Sinon.fakeServer
问题描述
我的问题如下:我想测试一种将大量数据上传到 AWS S3 存储桶的方法。问题是:我不想在每次测试时都上传数据,也不想关心环境中的凭据。所以我想设置 Sinon 的 fake-server 模块来模拟上传并返回与 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() 方法中,我从回调中解决了一个承诺。那么如何模拟上传过程呢?
编辑:这是 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);
});
});
}
}
解决方案
AWS.S3
Sinon 假服务器是您可以用来开发一个本身发出请求的客户端的东西,而不是像您正在做的那样围绕现有客户端的包装器。在这种情况下,您最好只是对行为进行存根,AWS.S3
而不是测试它发出的实际请求。这样您就可以避免测试AWS.S3
.
由于您使用的是 TypeScript 并且已经制作了 s3 客户端private
,因此您需要进行一些更改以将其公开给您的测试。否则,您将无法在没有 TS 编译器抱怨的情况下对其方法进行存根。params
出于类似的原因,您也将无法使用该对象编写断言。
由于我不经常使用 TS,因此我不太熟悉它的常见依赖注入技术,但您可以做的一件事是向您的S3Uploader
类添加可选的构造函数参数,该参数可以覆盖默认值s3
和arguments
属性,如下所示:
constructor(s3, params) {
if (s3) this.s3 = s3;
if (params) this.params = params;
}
之后,您可以创建一个存根实例并将其传递给您的测试实例,如下所示:
const s3 = sinon.createStubInstance(AWS.S3);
const params = { foo: 'bar' };
const uploader = new S3Uploader(s3, params);
一旦你有了存根实例,你就可以编写断言来确保upload
方法被调用的方式是你想要的:
sinon.assert.calledOnce(s3.upload);
sinon.assert.calledWith(s3.upload, sinon.match.same(params), sinon.match.func);
upload
您还可以使用sinon stub api影响该方法的行为。例如,让它像这样失败:
s3.upload.callsArgWith(1, null);
或者让它像这样成功:
const data = { whatever: 'data', you: 'want' };
s3.upload.callsArgWith(1, null, data);
您可能需要对每种情况进行完全独立的测试,使用实例before
挂钩来避免重复常见的设置内容。成功测试将包括简单地await
实现承诺并检查其结果是否是数据。失败测试将涉及try/catch
确保 promise 因正确错误而被拒绝。
此外,由于您似乎在这里进行实际的单元测试,我建议单独测试每个 S3Uploader 方法,而不是在一次大测试中调用它们。这大大减少了您需要涵盖的可能案例的数量,使您的测试更加直接。像这样的东西:
@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);
}
}
}
如果我用 mocha 来做这件事,我会将每个方法的测试分组到一个嵌套describe
块中。我不确定这是否受到鼓励甚至可能mocha-typescript
,但如果是这样,您可能会考虑它。
推荐阅读
- python - Scipy过滤器:强制SOS系数的最小值准备整数过滤器
- flutter - Flutter - iOS 构建失败
- angularjs - 如何将 ng-model 映射到 angulajs 中 ajax 的日期时间加载
- flutter - 从剪贴板颤动网络复制图像
- python - 通过 Cython 创建 DLL 时附加库的问题
- javascript - 复杂的 JSX 挑战 - 创建动态嵌套
- mysql - 如何从 laravel 中与关系的第一个表中选择特定列?
- scala - groupBy 并获取 scala 中多列的记录数
- javascript - 为什么 getState() 不是函数?
- java - gradle 依赖的 Noname 模块问题(Java 9)