如何用笑话模拟 S3?
Posted
技术标签:
【中文标题】如何用笑话模拟 S3?【英文标题】:How to mock S3 with jest? 【发布时间】:2020-09-01 22:50:31 【问题描述】:我正在尝试编写上传测试代码。
但我并没有低估如何正确使用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;
【问题讨论】:
【参考方案1】:这里是单元测试解决方案:
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') );
);
);
100% 覆盖率的单元测试结果:
PASS ***/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
【讨论】:
感谢您的说明性回答。从概念上讲,jest.fn().mockReturnThis()
做了什么?它可以让你链接调用吗?
@cischa 是的。它可以让你链接调用。
@slideshowp2 在我的测试中复制您的代码会从开玩笑返回此错误:ReferenceError: .../__tests__/user_handler.spec.ts: jest.mock()
的模块工厂不允许引用任何输出范围变量。无效的变量访问:mS3Instance 允许的对象:Array, ArrayBuffer, Atomics, BigInt, BigInt64Array, BigUint64Array, Boolean, Buffer, DTRACE_HTTP_CLIENT_REQUEST, DTRACE_HTTP_CLIENT_RESPONSE, DTRACE_HTTP_SERVER_REQUEST, DTRACE_HTTP_SERVER_RESPONSE, DTRACE_NET_SERVER_CONNECTION, DTRACE_NET_STREAM /跨度>
@BenjaminHeinke 这只是一个警告。将 mS3Instance
重命名为以 mock
开头的名称 - 例如 mockS3
。【参考方案2】:
这是我的解决方案:
jest.mock('aws-sdk', () =>
class mockS3
getSignedUrl(op, obj)
return 'url';
return
...jest.requireActual('aws-sdk'),
S3: mockS3,
;
);
【讨论】:
【参考方案3】:如果您使用的是 NestJS,它可能比您想象的要容易。
文件上传模块:
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
服务本身:
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;
最后是测试:
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);
);
);
);
【讨论】:
谢谢。我可以确认上述方法有效。以上是关于如何用笑话模拟 S3?的主要内容,如果未能解决你的问题,请参考以下文章