如何在 Jest 中为无服务器 Nodejs Lambda 模拟 AWS DynamoDB?
How to mock AWS DynamoDB in Jest for Serverless Nodejs Lambda?
我写了一个lambda如下。
handler.js
const aws = require('aws-sdk');
const dynamoDb = new aws.DynamoDB.DocumentClient();
const testHandler = async event => {
// some code
// ...
const user = await getUser(userId)
// ...
// some code
}
const promisify = foo => new Promise((resolve, reject) => {
foo((error, result) => {
if (error) {
reject(error)
} else {
resolve(result)
}
})
})
const getUser = (userId) => promisify(callback =>
dynamoDb.get({
TableName: 'test-table',
Key: {
"PK": `${userId}`,
"SK": `${userId}`
}
}, callback))
.then((user) => {
console.log(`Retrieved user: ${userId}`)
return user
})
module.exports = {
testHandler: testHandler,
getUser: getUser
}
我想编写一个单元测试来测试 getUser
函数,所以我尝试了以下方法。
handler.test.js
const handler = require('../handler');
const AWS = require('aws-sdk')
const dynamoDbGetParameterPromise = jest.fn().mockReturnValue({
promise: jest.fn().mockResolvedValue({
PK: 'userId-123', SK: 'userId-123'
})
})
AWS.DynamoDB.DocumentClient = jest.fn().mockImplementation(() => ({
get: dynamoDbGetParameterPromise
}))
describe('test getUser', () => {
beforeEach(() => {
jest.resetModules()
});
test('get user success', async () => {
const user = { PK: 'userId-123', SK: 'userId-123' };
const result = await handler.getUser(userId);
expect(result).toEqual(user);
});
});
错误如下
ConfigError: Missing region in config
105 |
106 | const getUser = (userId) => promisify(callback =>
> 107 | dynamoDb.get({
| ^
108 | TableName: 'test-table',
109 | Key: {
110 | "PK": 'userId-123',
似乎测试仍然使用 handler.js
中的 dynamoDb
而不是测试中的模拟。
关于如何正确连接模拟以测试功能的任何想法?
提前致谢!
您可以使用 jest.mock(moduleName, factory, options) 手动模拟 aws-sdk
模块。
例如
handler.js
:
const aws = require('aws-sdk');
const dynamoDb = new aws.DynamoDB.DocumentClient();
const promisify = (foo) =>
new Promise((resolve, reject) => {
foo((error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
const getUser = (userId) =>
promisify((callback) =>
dynamoDb.get(
{
TableName: 'test-table',
Key: {
PK: `${userId}`,
SK: `${userId}`,
},
},
callback,
),
).then((user) => {
console.log(`Retrieved user: ${userId}`);
return user;
});
module.exports = { getUser };
handler.test.js
:
const aws = require('aws-sdk');
const { getUser } = require('./handler');
jest.mock('aws-sdk', () => {
const mDocumentClient = { get: jest.fn() };
const mDynamoDB = { DocumentClient: jest.fn(() => mDocumentClient) };
return { DynamoDB: mDynamoDB };
});
const mDynamoDb = new aws.DynamoDB.DocumentClient();
describe('64564233', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should get user', async () => {
const mResult = { name: 'teresa teng' };
mDynamoDb.get.mockImplementationOnce((_, callback) => callback(null, mResult));
const actual = await getUser(1);
expect(actual).toEqual({ name: 'teresa teng' });
expect(mDynamoDb.get).toBeCalledWith(
{
TableName: 'test-table',
Key: {
PK: '1',
SK: '1',
},
},
expect.any(Function),
);
});
it('should handler error', async () => {
const mError = new Error('network');
mDynamoDb.get.mockImplementationOnce((_, callback) => callback(mError));
await expect(getUser(1)).rejects.toThrowError('network');
expect(mDynamoDb.get).toBeCalledWith(
{
TableName: 'test-table',
Key: {
PK: '1',
SK: '1',
},
},
expect.any(Function),
);
});
});
单元测试结果:
PASS src/Whosebug/64564233/handler.test.js (14.929s)
64564233
✓ should get user (23ms)
✓ should handler error (3ms)
console.log src/Whosebug/64564233/handler.js:433
Retrieved user: 1
------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
handler.js | 100 | 100 | 100 | 100 | |
------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 17.435s
源代码:https://github.com/mrdulin/jest-codelab/tree/master/src/Whosebug/64564233
您可以通过添加
使用 jest 的 auto-mock
jest.mock("aws-sdk");
然后 AWS.DynamoDB.DocumentClient
将是一个模拟的 class 所以你将能够模拟它的实现。因为我们希望它的 get 方法是一个接受任何东西作为第一个参数的函数(因为我们不会在模拟实现中对它做任何事情)和一个我们期望它被调用的回调 null
和 user
我们可以这样模拟它:
AWS.DynamoDB.DocumentClient.prototype.get.mockImplementation((_, cb) => {
cb(null, user);
});
我写了一个lambda如下。
handler.js
const aws = require('aws-sdk');
const dynamoDb = new aws.DynamoDB.DocumentClient();
const testHandler = async event => {
// some code
// ...
const user = await getUser(userId)
// ...
// some code
}
const promisify = foo => new Promise((resolve, reject) => {
foo((error, result) => {
if (error) {
reject(error)
} else {
resolve(result)
}
})
})
const getUser = (userId) => promisify(callback =>
dynamoDb.get({
TableName: 'test-table',
Key: {
"PK": `${userId}`,
"SK": `${userId}`
}
}, callback))
.then((user) => {
console.log(`Retrieved user: ${userId}`)
return user
})
module.exports = {
testHandler: testHandler,
getUser: getUser
}
我想编写一个单元测试来测试 getUser
函数,所以我尝试了以下方法。
handler.test.js
const handler = require('../handler');
const AWS = require('aws-sdk')
const dynamoDbGetParameterPromise = jest.fn().mockReturnValue({
promise: jest.fn().mockResolvedValue({
PK: 'userId-123', SK: 'userId-123'
})
})
AWS.DynamoDB.DocumentClient = jest.fn().mockImplementation(() => ({
get: dynamoDbGetParameterPromise
}))
describe('test getUser', () => {
beforeEach(() => {
jest.resetModules()
});
test('get user success', async () => {
const user = { PK: 'userId-123', SK: 'userId-123' };
const result = await handler.getUser(userId);
expect(result).toEqual(user);
});
});
错误如下
ConfigError: Missing region in config
105 |
106 | const getUser = (userId) => promisify(callback =>
> 107 | dynamoDb.get({
| ^
108 | TableName: 'test-table',
109 | Key: {
110 | "PK": 'userId-123',
似乎测试仍然使用 handler.js
中的 dynamoDb
而不是测试中的模拟。
关于如何正确连接模拟以测试功能的任何想法? 提前致谢!
您可以使用 jest.mock(moduleName, factory, options) 手动模拟 aws-sdk
模块。
例如
handler.js
:
const aws = require('aws-sdk');
const dynamoDb = new aws.DynamoDB.DocumentClient();
const promisify = (foo) =>
new Promise((resolve, reject) => {
foo((error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
const getUser = (userId) =>
promisify((callback) =>
dynamoDb.get(
{
TableName: 'test-table',
Key: {
PK: `${userId}`,
SK: `${userId}`,
},
},
callback,
),
).then((user) => {
console.log(`Retrieved user: ${userId}`);
return user;
});
module.exports = { getUser };
handler.test.js
:
const aws = require('aws-sdk');
const { getUser } = require('./handler');
jest.mock('aws-sdk', () => {
const mDocumentClient = { get: jest.fn() };
const mDynamoDB = { DocumentClient: jest.fn(() => mDocumentClient) };
return { DynamoDB: mDynamoDB };
});
const mDynamoDb = new aws.DynamoDB.DocumentClient();
describe('64564233', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should get user', async () => {
const mResult = { name: 'teresa teng' };
mDynamoDb.get.mockImplementationOnce((_, callback) => callback(null, mResult));
const actual = await getUser(1);
expect(actual).toEqual({ name: 'teresa teng' });
expect(mDynamoDb.get).toBeCalledWith(
{
TableName: 'test-table',
Key: {
PK: '1',
SK: '1',
},
},
expect.any(Function),
);
});
it('should handler error', async () => {
const mError = new Error('network');
mDynamoDb.get.mockImplementationOnce((_, callback) => callback(mError));
await expect(getUser(1)).rejects.toThrowError('network');
expect(mDynamoDb.get).toBeCalledWith(
{
TableName: 'test-table',
Key: {
PK: '1',
SK: '1',
},
},
expect.any(Function),
);
});
});
单元测试结果:
PASS src/Whosebug/64564233/handler.test.js (14.929s)
64564233
✓ should get user (23ms)
✓ should handler error (3ms)
console.log src/Whosebug/64564233/handler.js:433
Retrieved user: 1
------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
handler.js | 100 | 100 | 100 | 100 | |
------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 17.435s
源代码:https://github.com/mrdulin/jest-codelab/tree/master/src/Whosebug/64564233
您可以通过添加
使用 jest 的 auto-mockjest.mock("aws-sdk");
然后 AWS.DynamoDB.DocumentClient
将是一个模拟的 class 所以你将能够模拟它的实现。因为我们希望它的 get 方法是一个接受任何东西作为第一个参数的函数(因为我们不会在模拟实现中对它做任何事情)和一个我们期望它被调用的回调 null
和 user
我们可以这样模拟它:
AWS.DynamoDB.DocumentClient.prototype.get.mockImplementation((_, cb) => {
cb(null, user);
});