'How to mock S3 with jest?
I am tryng to code a test for upload.
But i am not understating how to properly use jest.mock('aws-sdk')
export class S3Service {
private readonly s3: S3;
private readonly bucket: string;
constructor(private readonly configService: ConfigService) {
this.s3 = new S3({
accessKeyId: this.configService.get(''),
secretAccessKey: this.configService.get(''),
region: this.configService.get(''),
});
this.bucket = this.configService.get('');
}
async upload(name: string, contentType: string, buffer: Buffer): Promise<string> {
const upload = await this.s3.upload({params...}).promise();
return upload;
}
}
Solution 1:[1]
Here is the unit test solution:
s3Service.ts
:
import { S3 } from 'aws-sdk';
export class S3Service {
private readonly s3: S3;
private readonly bucket: string;
constructor(private readonly configService) {
this.s3 = new S3({
accessKeyId: this.configService.get(''),
secretAccessKey: this.configService.get(''),
region: this.configService.get(''),
});
this.bucket = this.configService.get('');
}
public async upload(name: string, contentType: string, buffer: Buffer): Promise<any> {
const bucket = this.bucket;
const params = { Bucket: bucket, Key: 'key', Body: buffer };
const upload = await this.s3.upload(params).promise();
return upload;
}
}
s3Service.test.ts
:
import { S3Service } from './s3Service';
const mS3Instance = {
upload: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
jest.mock('aws-sdk', () => {
return { S3: jest.fn(() => mS3Instance) };
});
describe('61830632', () => {
it('should upload correctly', async () => {
const configService = {
get: jest
.fn()
.mockReturnValueOnce('accessKeyId')
.mockReturnValueOnce('secretAccessKey')
.mockReturnValueOnce('us-east')
.mockReturnValueOnce('bucket-dev'),
};
mS3Instance.promise.mockResolvedValueOnce('fake response');
const s3Service = new S3Service(configService);
const actual = await s3Service.upload('name', 'contentType', Buffer.from('ok'));
expect(actual).toEqual('fake response');
expect(mS3Instance.upload).toBeCalledWith({ Bucket: 'bucket-dev', Key: 'key', Body: Buffer.from('ok') });
});
});
unit test results with 100% coverage:
PASS stackoverflow/61830632/s3Service.test.ts (11.362s)
61830632
? should upload correctly (6ms)
--------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
s3Service.ts | 100 | 100 | 100 | 100 |
--------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.738s
Solution 2:[2]
Here is my solution:
jest.mock('aws-sdk', () => {
class mockS3 {
getSignedUrl(op, obj) {
return 'url';
}
}
return {
...jest.requireActual('aws-sdk'),
S3: mockS3
};
});
Solution 3:[3]
If you are using NestJS, it can be easier than you think.
File Upload Module:
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { S3 } from 'aws-sdk';
import { FileUploadController } from './file-upload.controller';
import { FileUploadRepository } from './file-upload.repository';
import { FileUploadService } from './file-upload.service';
@Module({
imports: [TypeOrmModule.forFeature([FileUploadRepository])],
controllers: [FileUploadController],
providers: [
FileUploadService,
{
provide: S3,
useFactory: (configService: ConfigService) =>
new S3({
accessKeyId: configService.get('AWS_ACCESS_KEY'),
secretAccessKey: configService.get('AWS_ACCESS_SECRET'),
region: configService.get('AWS_REGION'),
}),
},
],
})
export class FileUploadModule {}
The service itself:
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import { S3 } from 'aws-sdk';
import { v4 as uuid } from 'uuid';
import { FileUpload } from './file-upload.entity';
import { FileUploadRepository } from './file-upload.repository';
@Injectable()
export class FileUploadService {
constructor(
@InjectRepository(FileUploadRepository)
private readonly publicFileRepository: FileUploadRepository,
private readonly configService: ConfigService,
private readonly s3: S3,
) {}
async uploadPublicFile(
dataBuffer: Buffer,
filename: string,
): Promise<FileUpload> {
const uploadResult = await this.s3
.upload({
Bucket: this.configService.get('AWS_BUCKET_NAME'),
Body: dataBuffer,
Key: `${uuid()}-${filename}`,
})
.promise();
const createdFile = this.publicFileRepository.create({
key: uploadResult.Key,
url: uploadResult.Location,
});
await this.publicFileRepository.save(createdFile);
return createdFile;
}
async deletePublicFile(
publicFileId: string,
publicFileKey: string,
): Promise<FileUpload> {
const response = await this.s3
.deleteObject({
Bucket: this.configService.get('AWS_BUCKET_NAME'),
Key: publicFileKey,
})
.promise();
if (!response) {
throw new InternalServerErrorException(
`Could not delete file ${publicFileKey}`,
);
}
const { raw: deletedItem } = await this.publicFileRepository.delete(
publicFileId,
);
return deletedItem;
}
}
and then finally the test:
import { InternalServerErrorException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { S3 } from 'aws-sdk';
import { mockFileUpload } from './file-upload.mock';
import { FileUploadRepository } from './file-upload.repository';
import { FileUploadService } from './file-upload.service';
export const mockFileUploadRepository = () => ({
create: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
});
const mS3Instance = {
upload: jest.fn().mockReturnThis(),
promise: jest.fn(),
deleteObject: jest.fn().mockReturnThis(),
};
describe('FileUploadService', () => {
let service: FileUploadService;
let repository;
let s3Service;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
FileUploadService,
{
provide: FileUploadRepository,
useFactory: mockFileUploadRepository,
},
{
provide: ConfigService,
useValue: {
get: jest.fn(),
},
},
{
provide: S3,
useFactory: () => mS3Instance,
},
],
}).compile();
service = module.get<FileUploadService>(FileUploadService);
repository = module.get<FileUploadRepository>(FileUploadRepository);
repository = module.get<FileUploadRepository>(FileUploadRepository);
s3Service = module.get<S3>(S3);
});
describe('FileUploadService.uploadPublicFile', () => {
it('should create public file and throw no error', async () => {
repository.create.mockResolvedValue(mockFileUpload);
repository.save.mockResolvedValue(mockFileUpload);
s3Service.promise = jest.fn().mockResolvedValue({
Key: 'some-key',
Location: 'some-location',
});
const file = Buffer.alloc(513, '0');
const response = await service.uploadPublicFile(file, 'somefilename');
expect(response).toBeDefined();
});
});
describe('FileUploadService.deletePublicFile', () => {
it('should delete public file and throw no error', async () => {
repository.delete.mockResolvedValue({ raw: mockFileUpload });
repository.save.mockResolvedValue(mockFileUpload);
s3Service.promise = jest.fn().mockResolvedValue({
Key: 'some-key',
Location: 'some-location',
});
const response = await service.deletePublicFile('someid', 'somefilename');
expect(response).toBeDefined();
});
it('should not delete public file and throw InternalServerErrorException', async () => {
repository.delete.mockResolvedValue({ raw: mockFileUpload });
repository.save.mockResolvedValue(mockFileUpload);
s3Service.promise = jest.fn().mockResolvedValue(null);
const promise = service.deletePublicFile('someid', 'somefilename');
expect(promise).rejects.toThrow(InternalServerErrorException);
});
});
});
Solution 4:[4]
This would be basic approach to test the S3 aws-sdk.Lets say we have delete method for S3 in the AWS Lambda
DeleteS3Object.js
const AWS = require('aws-sdk');
const DeleteS3Object = (testBucketParams) => {
const s3 = new AWS.S3();
try {
const s3DeleteResponse = await s3.deleteObject(params).promise();
debugger;
console.log(`Delete S3 Object successfully:`, s3DeleteResponse);
resolve(s3DeleteResponse);
} catch (error) {
console.log(`Delete S3 Object failed:`, JSON.stringify(error));
reject(error);
}
};
exports.handler = deleteS3Object;
DeleteS3Object.test.js
const AWSMock = require('aws-sdk-mock');
const AWS = require('aws-sdk');
const DeleteS3Object = require('./DeleteS3Object').handler;
describe('Test Delete S3 object', () => {
beforeEach(() => {
AWSMock.setSDKInstance(AWS);
});
afterEach(() => {
AWSMock.restore('S3');
});
it('Should be successfully completed', async () => {
const successResult = { success: true };
AWSMock.mock('S3', 'deleteObject', (params, callback) => {
expect(params).toEqual({ Bucket: 'test', Key: 'test' });
return callback(null, successResult);
});
const paramsTest = { Bucket: 'test', Key: 'test' };
const finalResponse = await DeleteS3Object(paramsTest);
expect(finalResponse).toBe(successResult);
});
it('Should fail', async () => {
const failResult = { success: false };
AWSMock.mock('S3', 'deleteObject', (params, callback) => {
expect(params).toEqual({ Bucket: 'test', Key: 'test' });
return callback(failResult);
});
const paramsTest = {
basicS3ParamList: { Bucket: 'test', Key: 'test' },
timeToDelete: 1000
};
try {
await DeleteS3Object(paramsTest);
} catch (error) {
expect(error).toEqual(failResult);
}
});
})
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | slideshowp2 |
Solution 2 | mikemaccana |
Solution 3 | Lucas P |
Solution 4 | Siddharth Sunchu |