Trying to stub a function results in Descriptor for property is non-configurable and non-writable

Viewed 572

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?

2 Answers

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');
});

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.

Related