我正在尝试编写一个单元测试,从
@aws-sdk/s3-request-presigner包中对
getSignedUrl
函数进行存根,但是当我尝试使用 sinon
存根该函数时,我收到了错误:
- TypeError:属性 getSignedUrl 的描述符不可配置且不可写
const s3RequestSigner = require("@aws-sdk/s3-request-presigner");
const expect = require('chai').expect;
const sinon = require('sinon')
....
it('should throw an error when getSignedUrl rejects', async function() {
const sandbox = sinon.createSandbox();
sandbox.stub(s3RequestSigner, "getSignedUrl").rejects("fakeUrl");
sandbox.restore();
})
我正在使用 node.js 16 并编写 javascript 而不是 typescript。有没有办法模拟我的功能,否则我很难编写我的测试?
我为 ES6 模块提出了以下解决方法。您可以将
getSignedUrl
包装在您自己的模块中,然后模拟该模块。这种方法应该适用于 sinon 无法模拟“不可配置和不可写”方法的任何模块。
例如:
my-s3-client-internals.js - 您的自定义包装器模块
// You'll need to import the original method, assign it to
// a new const, then export that const
import { getSignedUrl as getSignedUrl_orig } from '@aws-sdk/s3-request-presigner';
export const getSignedUrl = getSignedUrl_orig;
my-s3-client.js - getSignedUrl 的消费者
// Import the method instead from your custom file
import { getSignedUrl } from './my-s3-client-internals';
// Call it however you normally would, for example:
export const getUrl(bucket, key) {
const command = new GetObjectCommand({ Bucket: bucket, Key: key });
return getSignedUrl(client, command, { expiresIn: 300 });
}
my-s3-client.spec.js - 消费者模块的单元测试
import { getUrl } from './my-s3-client';
import * as clientInternals from './my-s3-client-internals';
import sinon from 'sinon';
it('does something', () => {
// Mock the method exported from your wrapper module
sinon.stub(clientInternals, 'getSignedUrl')
.callsFake(async (client, command, options) => {
return 'fake-url';
});
// Then call your consumer method to test
const url = await getUrl('test-bucket', 'test-key');
expect(url).to.equal('fake-url');
});
所以我不会把这个作为官方答案,除非没有更好的解决方案,但这就是我的研究带来的解决方案。
issue与此相关:https://github.com/sinonjs/sinon/issues/2377
当 Object.descriptor 不可配置时,sinon 将抛出错误。
目前我找不到明显的解决方法。解决方法是使用proxyquire:
const sinon = require('sinon')
const proxyquire = require('proxyquire')
...
it('should throw an error when getSignedUrl rejects', async function() {
const fakeurl = 'hello world'
const fakeURL = sinon.stub().resolves(fakeurl)
const handler = proxyquire(
'../../handlers/presigned_url',
{
'@aws-sdk/s3-request-presigner': {
'getSignedUrl': async () => {
return fakeURL()
}
}
}
)
然后这将解决任何你想要的
fakeurl
。
另一种可能的解决方案是使用
mockery
。例如。嘲笑uuid
import { expect } from 'chai';
import mockery from 'mockery';
import sinon from 'sinon';
describe('domain/books', () => {
let createBook;
let uuidStub;
before(async () => {
mockery.enable({
warnOnReplace: false,
warnOnUnregistered: false,
});
uuidStub = sinon.stub();
mockery.registerMock('uuid', { v4: uuidStub });
({ createBook } = await import('../domain/books.js'));
});
afterEach(() => {
sinon.resetHistory();
});
after(() => {
sinon.restore();
mockery.disable();
mockery.deregisterAll();
});
describe('createBook', () => {
it('should save a book and return the id', () => {
const id = 'abc123';
uuidStub.returns(id);
const { id: bookId } = createBook({
title: 'My Book',
author: 'Jane Doe',
});
expect(bookId).to.equal(id);
});
});
});
嘲讽设置有点乏味,但图书馆救了我很多次。
容易存根
import { createSandbox } from 'sinon';
import * as s3PresignedPost from '@aws-sdk/s3-presigned-post/dist-cjs/createPresignedPost';
const sandbox = createSandbox();
const provider = new MyProvider();
describe('Generate get signed url', () => {
it('Error', async () => {
const errorStub = sandbox.stub(s3RequestPresigner, 'getSignedUrl').rejects(errorObject);
const error: Error = await provider.functionCallgetSignedUrlInside(bucketName, urlPath).catch((error) => error);
sandbox.assert.calledOnce(errorStub);
expect(error.message).to.eq(errorObject.message);
});
it('Success', async () => {
const url = `${baseUrl}/${bucketName}-${Date.now()}`;
const successStub = sandbox.stub(s3RequestPresigner, 'getSignedUrl').resolves(url);
const result = await storage.functionCallgetSignedUrlInside(bucketName, urlPath);
sandbox.assert.calledOnce(successStub);
expect(result).to.eq(url);
});
});