使用 Jest 正确模拟 S3 createPresignedPost
Correctly mocking S3 createPresignedPost with Jest
我正在尝试将单元测试添加到我的代码中。努力处理处理预签名 S3 url 的函数。我的功能如下。
'use strict';
const AWS = require('aws-sdk');
AWS.config.update({
region: process.env.AWS_DEFAULT_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
const createPresignedPost = ({ key, contentType }) => {
const s3 = new AWS.S3();
const params = {
Expires: 60,
Bucket: process.env.AWS_BUCKET_NAME,
Conditions: [['content-length-range', 100, 10000000]], // 100Byte - 10MB
Fields: {
'Content-Type': contentType,
'Cache-Control': 'max-age=31536000',
'Access-Control-Allow-Origin': '*',
key
}
};
return new Promise(async (resolve, reject) => {
s3.createPresignedPost(params, (err, data) => {
if (err) {
reject(err);
}
resolve(data);
});
});
};
module.exports = createPresignedPost;
至少,我只想开玩笑地考虑单元测试在某种程度上涵盖了这个功能,所以我的阈值保持在 CI 允许我的代码构建所需的最低覆盖率之上。
我做了以下:
const mockedCreatePresignedPost = jest.fn(() => ({
promise: jest.fn()
}));
jest.mock('aws-sdk', () => {
return {
S3: jest.fn(() => ({
createPresignedPost: mockedCreatePresignedPost
})),
config: {
update: jest.fn()
}
};
});
it('has to mock S3#createPresignedPost', /* async */ () => {
process.env.AWS_BUCKET_NAME = 'test1';
const params = {
key: 'test2',
contentType: 'application/json'
};
const s3params = {
Bucket: 'test1',
Conditions: [['content-length-range', 100, 10000000]],
Expires: 60,
Fields: {
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'max-age=31536000',
'Content-Type': params['contentType'],
key: params['key']
}
};
/* await */
createPresignedPost(params);
expect(mockedCreatePresignedPost).toHaveBeenCalledWith(s3params);
});
不过,这些测试不起作用。我得到以下不匹配:
expect(jest.fn()).toHaveBeenCalledWith(...expected)
- Expected
+ Received
{"Bucket": "test1", "Conditions": [["content-length-range", 100, 10000000]], "Expires": 60, "Fields": {"Access-Control-Allow-Origin": "*", "Cache-Control": "max-age=31536000", "Content-Type": "application/json", "key": "test2"}},
+ [Function anonymous],
Number of calls: 1
为什么我在用 await
调用 createPresignedPost()
时遇到问题,就像它应该工作一样?我如何摆脱这个“额外的”[Function anonymous]
,以便我的测试通过?
您应该使用回调模拟 s3.createPresignedPost
方法的实现。然后,您应该使用 mock Error
或 data
.
手动调用回调
此外,调用createPresignedPost
有两个参数:params
和匿名回调函数,可以用expect.any(Function)
来表示。
例如
index.js
:
'use strict';
const AWS = require('aws-sdk');
AWS.config.update({
region: process.env.AWS_DEFAULT_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
});
const createPresignedPost = ({ key, contentType }) => {
const s3 = new AWS.S3();
const params = {
Expires: 60,
Bucket: process.env.AWS_BUCKET_NAME,
Conditions: [['content-length-range', 100, 10000000]], // 100Byte - 10MB
Fields: {
'Content-Type': contentType,
'Cache-Control': 'max-age=31536000',
'Access-Control-Allow-Origin': '*',
key,
},
};
return new Promise((resolve, reject) => {
s3.createPresignedPost(params, (err, data) => {
if (err) {
reject(err);
}
resolve(data);
});
});
};
module.exports = createPresignedPost;
index.test.js
:
const createPresignedPost = require('./');
const mockedCreatePresignedPost = jest.fn();
jest.mock('aws-sdk', () => {
return {
S3: jest.fn(() => ({
createPresignedPost: mockedCreatePresignedPost,
})),
config: {
update: jest.fn(),
},
};
});
describe('68705353', () => {
it('has to mock S3#createPresignedPost', async () => {
mockedCreatePresignedPost.mockImplementation((params, callback) => {
callback(null, 'mocked data');
});
process.env.AWS_BUCKET_NAME = 'test1';
const params = {
key: 'test2',
contentType: 'application/json',
};
const s3params = {
Bucket: 'test1',
Conditions: [['content-length-range', 100, 10000000]],
Expires: 60,
Fields: {
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'max-age=31536000',
'Content-Type': params['contentType'],
key: params['key'],
},
};
const actual = await createPresignedPost(params);
expect(actual).toEqual('mocked data');
expect(mockedCreatePresignedPost).toHaveBeenCalledWith(s3params, expect.any(Function));
});
it('should handle error', async () => {
mockedCreatePresignedPost.mockImplementation((params, callback) => {
callback(new Error('network'));
});
process.env.AWS_BUCKET_NAME = 'test1';
const params = {
key: 'test2',
contentType: 'application/json',
};
const s3params = {
Bucket: 'test1',
Conditions: [['content-length-range', 100, 10000000]],
Expires: 60,
Fields: {
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'max-age=31536000',
'Content-Type': params['contentType'],
key: params['key'],
},
};
await expect(createPresignedPost(params)).rejects.toThrowError('network');
expect(mockedCreatePresignedPost).toHaveBeenCalledWith(s3params, expect.any(Function));
});
});
测试结果:
PASS examples/68705353/index.test.js (8.834 s)
68705353
✓ has to mock S3#createPresignedPost (4 ms)
✓ should handle error (4 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 9.648 s
我正在尝试将单元测试添加到我的代码中。努力处理处理预签名 S3 url 的函数。我的功能如下。
'use strict';
const AWS = require('aws-sdk');
AWS.config.update({
region: process.env.AWS_DEFAULT_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
const createPresignedPost = ({ key, contentType }) => {
const s3 = new AWS.S3();
const params = {
Expires: 60,
Bucket: process.env.AWS_BUCKET_NAME,
Conditions: [['content-length-range', 100, 10000000]], // 100Byte - 10MB
Fields: {
'Content-Type': contentType,
'Cache-Control': 'max-age=31536000',
'Access-Control-Allow-Origin': '*',
key
}
};
return new Promise(async (resolve, reject) => {
s3.createPresignedPost(params, (err, data) => {
if (err) {
reject(err);
}
resolve(data);
});
});
};
module.exports = createPresignedPost;
至少,我只想开玩笑地考虑单元测试在某种程度上涵盖了这个功能,所以我的阈值保持在 CI 允许我的代码构建所需的最低覆盖率之上。
我做了以下:
const mockedCreatePresignedPost = jest.fn(() => ({
promise: jest.fn()
}));
jest.mock('aws-sdk', () => {
return {
S3: jest.fn(() => ({
createPresignedPost: mockedCreatePresignedPost
})),
config: {
update: jest.fn()
}
};
});
it('has to mock S3#createPresignedPost', /* async */ () => {
process.env.AWS_BUCKET_NAME = 'test1';
const params = {
key: 'test2',
contentType: 'application/json'
};
const s3params = {
Bucket: 'test1',
Conditions: [['content-length-range', 100, 10000000]],
Expires: 60,
Fields: {
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'max-age=31536000',
'Content-Type': params['contentType'],
key: params['key']
}
};
/* await */
createPresignedPost(params);
expect(mockedCreatePresignedPost).toHaveBeenCalledWith(s3params);
});
不过,这些测试不起作用。我得到以下不匹配:
expect(jest.fn()).toHaveBeenCalledWith(...expected)
- Expected
+ Received
{"Bucket": "test1", "Conditions": [["content-length-range", 100, 10000000]], "Expires": 60, "Fields": {"Access-Control-Allow-Origin": "*", "Cache-Control": "max-age=31536000", "Content-Type": "application/json", "key": "test2"}},
+ [Function anonymous],
Number of calls: 1
为什么我在用 await
调用 createPresignedPost()
时遇到问题,就像它应该工作一样?我如何摆脱这个“额外的”[Function anonymous]
,以便我的测试通过?
您应该使用回调模拟 s3.createPresignedPost
方法的实现。然后,您应该使用 mock Error
或 data
.
此外,调用createPresignedPost
有两个参数:params
和匿名回调函数,可以用expect.any(Function)
来表示。
例如
index.js
:
'use strict';
const AWS = require('aws-sdk');
AWS.config.update({
region: process.env.AWS_DEFAULT_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
});
const createPresignedPost = ({ key, contentType }) => {
const s3 = new AWS.S3();
const params = {
Expires: 60,
Bucket: process.env.AWS_BUCKET_NAME,
Conditions: [['content-length-range', 100, 10000000]], // 100Byte - 10MB
Fields: {
'Content-Type': contentType,
'Cache-Control': 'max-age=31536000',
'Access-Control-Allow-Origin': '*',
key,
},
};
return new Promise((resolve, reject) => {
s3.createPresignedPost(params, (err, data) => {
if (err) {
reject(err);
}
resolve(data);
});
});
};
module.exports = createPresignedPost;
index.test.js
:
const createPresignedPost = require('./');
const mockedCreatePresignedPost = jest.fn();
jest.mock('aws-sdk', () => {
return {
S3: jest.fn(() => ({
createPresignedPost: mockedCreatePresignedPost,
})),
config: {
update: jest.fn(),
},
};
});
describe('68705353', () => {
it('has to mock S3#createPresignedPost', async () => {
mockedCreatePresignedPost.mockImplementation((params, callback) => {
callback(null, 'mocked data');
});
process.env.AWS_BUCKET_NAME = 'test1';
const params = {
key: 'test2',
contentType: 'application/json',
};
const s3params = {
Bucket: 'test1',
Conditions: [['content-length-range', 100, 10000000]],
Expires: 60,
Fields: {
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'max-age=31536000',
'Content-Type': params['contentType'],
key: params['key'],
},
};
const actual = await createPresignedPost(params);
expect(actual).toEqual('mocked data');
expect(mockedCreatePresignedPost).toHaveBeenCalledWith(s3params, expect.any(Function));
});
it('should handle error', async () => {
mockedCreatePresignedPost.mockImplementation((params, callback) => {
callback(new Error('network'));
});
process.env.AWS_BUCKET_NAME = 'test1';
const params = {
key: 'test2',
contentType: 'application/json',
};
const s3params = {
Bucket: 'test1',
Conditions: [['content-length-range', 100, 10000000]],
Expires: 60,
Fields: {
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'max-age=31536000',
'Content-Type': params['contentType'],
key: params['key'],
},
};
await expect(createPresignedPost(params)).rejects.toThrowError('network');
expect(mockedCreatePresignedPost).toHaveBeenCalledWith(s3params, expect.any(Function));
});
});
测试结果:
PASS examples/68705353/index.test.js (8.834 s)
68705353
✓ has to mock S3#createPresignedPost (4 ms)
✓ should handle error (4 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 9.648 s