Reputation: 3498
I'm trying to write out a unit test that stubs the getSignedUrl
function from the @aws-sdk/s3-request-presigner package, however when I try stub out the function with sinon
, I receive the error:
- TypeError: Descriptor for property getSignedUrl is non-configurable and non-writable
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();
})
I'm using node.js 16 and writing javascript rather than typescript. Is there a way to mock out my function, i'm struggling to write my tests otherwise?
Upvotes: 9
Views: 11221
Reputation: 1
Easy to stub
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);
});
});
Upvotes: -1
Reputation: 81
Another possible solution is to use mockery
. E.g. to mock 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);
});
});
});
The mockery setup is a bit tedious, but the library saved me a number of times.
Upvotes: 0
Reputation: 41
I came up with the following workaround for ES6 modules. You can wrap getSignedUrl
in your own module and mock that module instead. This approach should work for any modules where sinon is unable to mock a "non-configurable and non-writable" method.
For example:
my-s3-client-internals.js - Your custom wrapper module
// 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 - Consumer of 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 - Unit tests for the consumer module
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');
});
Upvotes: 4
Reputation: 3498
So I won't make this the official answer, unless there are no better solutions, but this is what my research has brought about a solution.
The issue is related to this: https://github.com/sinonjs/sinon/issues/2377
Where sinon will throw an error when the Object.descriptor is non-configurable.
There is no obvious way around that currently, that I can find. The way to solve it is to use 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()
}
}
}
)
This will then resolve with whatever you want fakeurl
to be.
Upvotes: 3