我正在尝试对 lambda 函数进行单元测试,但不知道如何模拟 lambda callback
以使其停止代码执行。正在调用我模拟的 callback
,在 lambda 的情况下,它将立即返回响应。但在我的单元测试中,它继续执行代码,但出现错误:
TypeError: Cannot read property 'body' of undefined
我对 Jest 比较陌生,所以不确定如何继续。
example.js
(lambda 代码)
// dependencies
const got = require('got');
// lambda handler
const example = async (event, context, callback) => {
// message placeholder
let message;
// set request options
const gotOptions = {
json: {
process: event.process
},
responseType: 'json'
};
// http response data
const res = await got.post('https://some.url/api/process', gotOptions).catch((error) => {
message = 'error calling process';
// log and return the error
console.log(message, error);
callback(message);
});
// res.body is causing the error in the test since
// this code still executes after callbacks triggered
message = `Process ${event.process} is: ${res.body.active}`;
callback(null, message);
};
// export example
exports.example = example;
example.test.js
(单元测试代码)
// get the lib we want to test
const example = require('./example');
// setup mocks
jest.mock('got');
// mock our lambda callback
const callback = jest.fn();
// import the modules we want to mock
const got = require('got');
// set default event
let event = {
process: 1
};
// set default context
const context = {};
// run before each test
beforeEach(() => {
// set default got.post response
got.post.mockReturnValue(Promise.resolve({
body: {
active: true
}
}));
});
// test artifact api
describe('[example]', () => {
...other tests that pass...
test('error calling process api', async () => {
let error = 'error calling process';
// set got mock response for this test to error
got.post.mockReturnValue(Promise.reject(error));
// function we want to test w/ mock data
await example.example(event, context, callback);
// test our callback function to see if it matches our desired expectedResponse
expect(callback).toHaveBeenCalledWith(error);
});
});
答案 0 :(得分:1)
这里好像有两个问题
混合async
和non-async
lambda 函数可以是 async
或 non-async
。
async
处理程序使用可以返回或抛出的 async
函数。如果返回 Promise
,lambda 函数将等待 Promise
解析或拒绝并返回结果。
non-async
函数使用回调作为第三个参数并返回传递给回调的结果。
在这种情况下,函数是 async
但也使用回调。它应该使用 async
函数或回调函数,但不能同时使用两者。
我模拟的 callback
被调用,在 lambda 的情况下会立即返回响应。
默认情况下,lambda 函数在调用回调时不会立即返回响应。
如果您使用 non-async handler,请注意“执行会一直持续到事件循环为空或函数超时。在所有事件循环任务完成之前,不会将响应发送到调用者。 "
(请注意,您可以将 callbackWaitsForEmptyEventLoop
设置为 false
以使 lambda 函数立即返回,但这不是真正的解决方案,因为进程的状态将被冻结并将重新启动下次调用时处于该确切状态,因此错误只会在下次调用时发生。)
因此最佳实践是确保 non-async
lambda 函数始终能够运行完成,因为传递给回调的值在事件循环为空之前实际上不会传回。
在上面的示例中,它可能看起来在调用 callback
后执行停止,但这只是因为看起来 AWS 没有报告 {{ 1}} 调用时出错。
这是一个简单的非异步处理程序来演示:
callback
在这种情况下,我将删除 exports.handler = (event, context, callback) => {
console.log('starting'); // logged
callback('this error gets reported'); // callback called with an error
console.log('still running'); // logged
throw new Error('this error is not reported'); // not reported
console.log('ending'); // not logged
};
参数并使用纯粹的 callback
函数。
像这样:
async
然后你可以像这样直接测试返回的const got = require('./got');
const example = async (event, context) => {
const gotOptions = {
json: {
process: event.process
},
responseType: 'json'
};
return got.post('https://some.url/api/process', gotOptions)
.then(res => `Process ${event.process} is: ${res.body.active}`)
.catch((error) => {
// log, format the returned error, etc.
// (or just remove the catch to return the error as-is)
console.log(error);
throw new Error(error);
});
};
exports.example = example;
:
Promise
答案 1 :(得分:0)
1-在根项目中添加文件夹 __mocks__
2-在 got.js
文件夹中添加文件 __mocks__
3-将代码添加到 got.js
:
module.exports = {
post: (url, options) => {
return new Promise((res, rej) => {
res({ body: { active: 'test' } })
})
}
}
4- 在测试文件中:
let example = require('./example');
let callback_arg1 = ''
let callback_arg2 = ''
let event = {
process: 1
};
let context = {};
let callback = (arg1, arg2) => {
callback_arg1 = arg1
callback_arg2 = arg2
};
describe('example', () => {
test('error calling process api', async () => {
await example.example(event, context, callback);
expect(callback_arg1).toBe(null)
expect(callback_arg2).toBe('Process 1 is: test')
});
});
答案 2 :(得分:0)
您需要模拟 callback
函数的实现。为了在错误处理后停止执行代码,您需要throw new Error()
,并使用await expect(example.example(event, context, callback)).rejects.toThrow(error);
捕获错误以避免测试失败。这样我们就可以模拟aws lambda
例如
example.js
:
const got = require('got');
const example = async (event, context, callback) => {
let message;
const gotOptions = {
json: {
process: event.process,
},
responseType: 'json',
};
const res = await got.post('https://some.url/api/process', gotOptions).catch((error) => {
callback(error);
});
console.log('process');
message = `Process ${event.process} is: ${res.body.active}`;
callback(null, message);
};
exports.example = example;
example.test.js
:
const example = require('./example');
const got = require('got');
jest.mock('got');
const callback = jest.fn().mockImplementation((errorMsg) => {
if (errorMsg) throw new Error(errorMsg);
});
const event = { process: 1 };
const context = {};
describe('[example]', () => {
test('error calling process api', async () => {
let error = 'error calling process';
got.post.mockRejectedValueOnce(error);
await expect(example.example(event, context, callback)).rejects.toThrow(error);
expect(callback).toHaveBeenCalledWith(error);
});
test('should success', async () => {
got.post.mockResolvedValueOnce({
body: { active: true },
});
await example.example(event, context, callback);
expect(callback).toHaveBeenCalledWith(null, 'Process 1 is: true');
});
});
测试结果:
PASS examples/66567679/example.test.js
[example]
✓ error calling process api (5 ms)
✓ should success (10 ms)
console.log
process
at examples/66567679/example.js:17:11
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
example.js | 100 | 100 | 100 | 100 |
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.966 s, estimated 4 s
答案 3 :(得分:0)
Jest 支持使用回调的测试代码。您的测试可以接受 done
参数。
请参阅笑话文档 here。
将该模式应用于您的测试可能如下所示:
describe('[example]', () => {
test('error calling process api', done => {
const error = 'error calling process';
got.post.mockReturnValue(Promise.reject(error));
await example.example(event, context, callbackError => {
// used try...catch pattern from jest docs
try {
expect(callbackError).toEqual(error);
} catch (e) {
done(e);
}
});
});
});
备注
async
并接受 done
参数。done()
,测试将因超时而失败。expect().toEqual...
的结果。如果 expect
失败,则不会调用 done
并且测试将超时,然后您将收到超时错误而不是来自 expect
的更有用的错误。这将使您无需完全切换即可使用 Promise。
一旦您使用了该测试和代码,您可能会在主处理程序代码中遇到控制流错误。
在catch 中调用callback(error)
后,代码中的非错误路径挂起并失败。失败,因为捕获后结果未定义。
Jest/node 会将此报告为未解决的承诺错误并警告您:
<块引用>将来,未处理的 promise 拒绝将以非零退出代码终止 Node.js 进程。
我的建议是,如果您要 await
api 调用,那么不要使用 .catch
,而是在它周围放置一个 try...catch
。
例如
try {
const res = await got.post('https://some.url/api/process', gotOptions);
message = `Process ${event.process} is: ${res.body.active}`;
callback(null, message);
} catch (error) {
message = 'error calling process';
console.log(message, error);
callback(message);
}
或者去掉 await
并像承诺一样使用它。
例如
got.post('https://some.url/api/process', gotOptions)
.then(res => {
message = `Process ${event.process} is: ${res.body.active}`;
callback(null, message);
}).catch((error) => {
message = 'error calling process';
console.log(message, error);
callback(message);
});