Inadequate Use of Mock Implementation in aws-sdk-mock for s3.upload

Param
Type
Optional/Required
Description
string
Required
AWS service to mock e.g. SNS, DynamoDB, S3
string
Required
method on AWS service to mock e.g. ‘publish’ (for SNS), ‘putItem’ for ‘DynamoDB’
string or function
Required
A string or function to replace the method
Removes the mock to restore the specified AWS service
Param
Type
Optional/Required
Description
string
Optional
AWS service to restore – If only the service is specified, all the methods are restored
string
Optional
Method on AWS service to restore
If
is called without arguments (
) then all the services and their associated methods are restored
i.e. equivalent to a ‘restore all’ function.
Param
Type
Optional/Required
Description
string
Required
AWS service to mock e.g. SNS, DynamoDB, S3
string
Required
method on AWS service to mock e.g. ‘publish’ (for SNS), ‘putItem’ for ‘DynamoDB’
string or function
Required
A string or function to replace the method
Explicitly set the require path for the
Param
Type
Optional/Required
Description
string
Required
Path to a nested AWS SDK node module
Explicitly set the
instance to use
Param
Type
Optional/Required
Description
object

Question:

To upload objects to s3, I utilize this wrapper function.

// upload.js
async function uploadToS3 (body, bucket, key) {
    console.log(`Uploading data to s3://${bucket}${key}`)
    await s3
        .upload({
                Body: body,
                Bucket: bucket,
                Key: key
            })
        .promise()
        .then((data) => {
                console.log(`Successfully uploaded data to ${data.Location}`)
            }
        )
        .catch((err) => {
            console.error(err)
        })
}

I am attempting to utilize the
aws-sdk
-mock to simulate the function’s behavior. This way, when the function is executed, it won’t actually send any items to s3. I can then confirm whether it logged a success or failure.

Here is what I have tried

// upload.test.js
describe( 'uploadToS3', () => {
    test('should upload a file to s3', async () => {
        const body = 'test|data'
        const bucket = 'testBucket'
        const key = 'testKey'
        AWS.mock( 'S3', 'upload',function (params, callback){
            callback(null, 'successfully put item in s3');
        });
        await util.uploadToS3(body, bucket, key)
    })
})

Regrettably, even though I invoke the

uploadToS3

function, it continues to utilize the current s3.upload implementation and endeavors to transmit the object to S3. I have previously employed comparable mocking techniques with other
AWS services
and achieved favorable outcomes, but this particular one appears to be causing difficulties for me.

Is there a way to simulate the AWS.S3.upload function and the following

.then

and

.catch

functions?

Perhaps, Jest could be an alternative solution for this.


Solution 1:

Instead of utilizing the

aws-sdk-mock

package, you have the option to employ the jest.mock(moduleName, factory, options) method to independently mock the

aws-sdk

package.

E.g.


upload.js

:

import AWS from 'aws-sdk';
const s3 = new AWS.S3();
export async function uploadToS3(body, bucket, key) {
  console.log(`Uploading data to s3://${bucket}${key}`);
  await s3
    .upload({
      Body: body,
      Bucket: bucket,
      Key: key,
    })
    .promise()
    .then((data) => {
      console.log(`Successfully uploaded data to ${data.Location}`);
    })
    .catch((err) => {
      console.error(err);
    });
}


upload.test.js

:

import { uploadToS3 } from './upload';
import AWS from 'aws-sdk';
jest.mock('aws-sdk', () => {
  const mockedS3 = {
    upload: jest.fn().mockReturnThis(),
    promise: jest.fn(),
  };
  return { S3: jest.fn(() => mockedS3) };
});
describe('uploadToS3', () => {
  afterAll(() => {
    jest.resetAllMocks();
  });
  afterEach(() => {
    jest.clearAllMocks();
  });
  it('should upload file to s3', async () => {
    const mockedS3 = new AWS.S3();
    const logSpy = jest.spyOn(console, 'log');
    mockedS3.promise.mockResolvedValueOnce({ Location: 'us' });
    const body = 'test|data';
    const bucket = 'testBucket';
    const key = 'testKey';
    await uploadToS3(body, bucket, key);
    expect(mockedS3.upload).toBeCalledWith({ Body: body, Bucket: bucket, Key: key });
    expect(mockedS3.promise).toBeCalledTimes(1);
    expect(logSpy).toBeCalledWith('Successfully uploaded data to us');
  });
  it('should handle error when upload file to s3 failed', async () => {
    const mockedS3 = new AWS.S3();
    const errorLogSpy = jest.spyOn(console, 'error');
    const mError = new Error('network');
    mockedS3.promise.mockRejectedValueOnce(mError);
    const body = 'test|data';
    const bucket = 'testBucket';
    const key = 'testKey';
    await uploadToS3(body, bucket, key);
    expect(mockedS3.upload).toBeCalledWith({ Body: body, Bucket: bucket, Key: key });
    expect(mockedS3.promise).toBeCalledTimes(1);
    expect(errorLogSpy).toBeCalledWith(mError);
  });
});

unit test result:

 PASS  examples/67204024/upload.test.js (6.42 s)
  uploadToS3
    ✓ should upload file to s3 (16 ms)
    ✓ should handle error when upload file to s3 failed (6 ms)
  console.log
    Uploading data to s3://testBuckettestKey
      at console. (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
  console.log
    Successfully uploaded data to us
      at console. (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
  console.log
    Uploading data to s3://testBuckettestKey
      at console. (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
  console.error
    Error: network
        at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/examples/67204024/upload.test.js:35:20
        at Generator.next ()
        at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/examples/67204024/upload.test.js:8:71
        at new Promise ()
        at Object..__awaiter (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/examples/67204024/upload.test.js:4:12)
        at Object. (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/examples/67204024/upload.test.js:32:70)
        at Object.asyncJestTest (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)
        at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:45:12
        at new Promise ()
        at mapper (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:28:19)
        at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:75:41
        at processTicksAndRejections (internal/process/task_queues.js:93:5)
      16 |     })
      17 |     .catch((err) => {
    > 18 |       console.error(err);
         |               ^
      19 |     });
      20 | }
      21 | 
      at console. (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
      at examples/67204024/upload.js:18:15
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        6.896 s, estimated 7 s


Solution 2:

This is also an option:

const getObjectStub = AWS.S3.prototype.getObject = Sinon.stub();
    getObjectStub.yields(null, {
        AcceptRanges: "bytes", 
        ContentLength: 3191, 
        ContentType: "image/jpeg", 
        Metadata: {
            ...
        }, 
        TagCount: 2, 
        VersionId: "null"
    }
);

Frequently Asked Questions