我的问题如下:我想测试一种将大量数据上传到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);
});
});
}
}
答案 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
是否会鼓励或什至可以,但是如果是这样,您可以考虑一下。