dwyl / aws-sdk-mock

:rainbow: AWSomocks for Javascript/Node.js aws-sdk tested, documented & maintained. Contributions welcome!
Apache License 2.0
1.11k stars 110 forks source link

Unable to mock S3 using jest #155

Open ffxsam opened 5 years ago

ffxsam commented 5 years ago

I'm having a lot of trouble getting this to work. The actual S3.headObject is getting called, despite me mocking it.

import AWS from 'aws-sdk-mock';
import S3 from 'aws-sdk/clients/s3';
import { Readable } from 'stream';
import { S3Object } from '../src/s3-object';
import { MOCK_USER_ID, MOCK_BUCKET, MOCK_KEY } from './constants';

describe('s3-object', () => {
  let s3obj: S3Object;

  beforeAll(() => {
    AWS.mock('S3', 'headObject', (params, callback) => {
      console.log('it works');
    });
  });

  beforeEach(() => {
    s3obj = new S3Object({
      bucket: 'test-bucket',
      key: 'some-folder/some-file.wav',
    });
  });

  it('gets metadata', async () => {
    const metadata = await s3obj.getMetadata();
    expect(metadata).toEqual({ userid: MOCK_USER_ID });
  });

My "gets metadata" test is calling the real S3.headObject instead of the mocked version, for some reason. Here's the getMetadata method in my S3Object class along with the constructor:

  constructor({ bucket, key }: S3ObjectProps) {
    /**
     * Instantiate S3 on the instance; future-proof to allow per-object config
     * changes.
     */
    this.s3 = new AWS.S3({ apiVersion: API_VERSION });
    this.bucket = bucket;
    this.key = key;
  }

  public async getMetadata(): Promise<AWS.S3.Metadata> {
    const data = await this.s3
      .headObject({
        Bucket: this.bucket,
        Key: this.key,
      })
      .promise();

    return data.Metadata || {};
  }
DanHulton commented 5 years ago

@ffxsam Did you ever solve this? I'm running into the same issue, though with the S3.upload method (though I assume that doesn't make really any difference).

Jest 24.1.0, AWS-SDK 2.397.0.

DanHulton commented 5 years ago

I actually just sorted this out - you need to create your s3 object INSIDE the function you want to mock. So move

this.s3 = new AWS.S3({ apiVersion: API_VERSION });

from constructor() to getMetadata() and hopefully that should allow mocking to work. It was what finally worked for me.

rohitshetty commented 5 years ago

I think the mocking of the service and calling of the mocked function should happen in same function according to the docs. Moving the code as suggested by @DanHulton would work, but I don't think it's wise to touch the Unit under test to make tests pass.

Here is how I would do it.

import AWS from 'aws-sdk-mock';
import S3 from 'aws-sdk/clients/s3';
import { Readable } from 'stream';
import { S3Object } from '../src/s3-object';
import { MOCK_USER_ID, MOCK_BUCKET, MOCK_KEY } from './constants';

describe('s3-object', () => {

const makeS3Obj = () => {
  AWS.mock('S3', 'headObject', (params, callback) => {
    console.log('it works');
  });

  return new S3Object({
      bucket: 'test-bucket',
      key: 'some-folder/some-file.wav',
});

}

  afterEach(() => {
    AWS.restore();
  });

  it('gets metadata', async () => {
    const s3Obj = makeS3Obj()
    const metadata = await s3obj.getMetadata();
    expect(metadata).toEqual({ userid: MOCK_USER_ID });
  })
rohitshetty commented 5 years ago

Another general comment, not releated to this code would be to use some sort of dependency injection so that you are in control of the dependencies passed.