[英]Mock Lambda callback in Jest? (Cannot read property body of undefined)
I'm trying to unit test a lambda function but can't figure out how to mock the lambda callback
so it stops code execution.我正在尝试对 lambda function 进行单元测试,但无法弄清楚如何模拟 lambda callback
,因此它会停止代码执行。 The callback
I mock up is being called, which in the case of a lambda would immediately return the response.正在调用我模拟的callback
,在 lambda 的情况下将立即返回响应。 In my unit tests though, it continues executing code and I get the error:但是在我的单元测试中,它继续执行代码并且我收到错误:
TypeError: Cannot read property 'body' of undefined
I'm relatively new to Jest so not sure how to proceed.我对 Jest 比较陌生,所以不知道如何继续。
example.js
(lambda code) 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
(unit test code) 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);
});
});
You need to mock the implementation of the callback
function.您需要模拟callback
function 的实现。 In order to stop executing the code after error handling, you need to throw new Error()
, and use await expect(example.example(event, context, callback)).rejects.toThrow(error);
为了在错误处理后停止执行代码,需要throw new Error()
,并使用await expect(example.example(event, context, callback)).rejects.toThrow(error);
to catch the error to avoid test failure.捕获错误以避免测试失败。 In this way, we can simulate the behavior of aws lambda这样我们就可以模拟aws lambda的行为
Eg例如
example.js
: 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
: 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');
});
});
test result:测试结果:
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
Looks like there are two issues here看来这里有两个问题
Mixing async
and non-async
混合async
和non-async
A lambda function can either be async
or non-async
. lambda function 可以是async
或non-async
。
An async
handler uses an async
function that can either return or throw. async
处理程序使用可以返回或抛出的async
function。 If a Promise
is returned the lambda function will wait for the Promise
to resolve or reject and return the result.如果返回Promise
,则 lambda function 将等待Promise
解决或返回结果。
A non-async
function uses a callback as the third argument and returns the result passed to the callback. non-async
function 使用回调作为第三个参数,并返回传递给回调的结果。
In this case the function is async
but is also using a callback.在这种情况下,function 是async
的,但也使用回调。 It should use either an async
function or a callback function but not both.它应该使用async
function或回调 function 但不能同时使用两者。
The
callback
I mock up is being called, which in the case of a lambda would immediately return the response.正在调用我模拟的callback
,在 lambda 的情况下将立即返回响应。
By default a lambda function does not immediately return the response when the callback is called.默认情况下,调用回调时,lambda function不会立即返回响应。
If you use a non-async handler , note that "execution continues until the event loop is empty or the function times out. The response isn't sent to the invoker until all event loop tasks are finished. "如果您使用非异步处理程序,请注意“执行将持续到事件循环为空或 function 超时。在所有事件循环任务完成之前,不会将响应发送到调用程序。 ”
(Note that you could set callbackWaitsForEmptyEventLoop
to false
to get the lambda function to return right away, but this isn't a real solution since the state of the process will be frozen and will restart at that exact state the next time it is invoked so the error would just happen on the next invocation.) (Note that you could set callbackWaitsForEmptyEventLoop
to false
to get the lambda function to return right away, but this isn't a real solution since the state of the process will be frozen and will restart at that exact state the next time it is invoked so错误只会在下一次调用时发生。)
So best practice is to ensure that a non-async
lambda function is always able to run to completion since the value passed to the callback isn't actually passed back until the event loop is empty.因此,最佳实践是确保non-async
lambda function 始终能够运行到完成,因为传递给回调的值在事件循环为空之前实际上不会被传回。
In the example above it might look like execution stops after the callback
is invoked, but that is only because it looks like AWS does not report info on exceptions thrown after the callback
is called with an error.在上面的示例中,调用callback
后执行可能会停止,但这仅仅是因为 AWS 似乎没有报告在callback
后引发的异常信息,但出现错误。
Here is a simple non-async handler to demonstrate:这是一个简单的非异步处理程序来演示:
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
};
In this case I would just remove the callback
argument and go with a purely async
function.在这种情况下,我只需使用纯async
function 删除callback
参数和 go。
Something like this:像这样的东西:
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;
Then you can test the returned Promise
directly like this:然后可以像这样直接测试返回的Promise
:
const example = require('./example');
jest.mock('./got');
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]', () => {
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 expect(example.example(event, context)).rejects.toThrow(error); // SUCCESS
});
});
1-add folder __mocks__
in root project 1-在根项目中添加文件夹__mocks__
2-add file got.js
in __mocks__
folder 2-在__mocks__
文件夹中添加文件got.js
3-add code to got.js
: 3-将代码添加到got.js
:
module.exports = {
post: (url, options) => {
return new Promise((res, rej) => {
res({ body: { active: 'test' } })
})
}
}
4- in test file: 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')
});
});
Jest supports testing code that uses callbacks. Jest 支持使用回调的测试代码。 Your test can accept a done
parameter.您的测试可以接受done
参数。
See the jest documentation here .请参阅此处的笑话文档。
Applying that pattern to your test it could look like the following:将该模式应用于您的测试,它可能如下所示:
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);
}
});
});
});
Notes笔记
async
and accepts a done
parameter.测试不再是async
的,并且接受done
参数。done()
is not called.如果没有调用done()
,测试将失败并超时。expect().toEqual...
in the callback.这是回调中expect().toEqual...
的结果。 If the expect
fails, done
will not be called and the test will timeout, and then you'll get the timeout error rather than the more useful error from the expect
.如果expect
失败,将不会调用done
并且测试将超时,然后您将收到超时错误,而不是来自expect
的更有用的错误。This will get you going without having to switch it all over to use Promises.这将使您无需全部切换即可使用 Promises。
Once you've played with that test and the code a bit you may run into a control flow bug in your main handler code.一旦您使用了该测试和代码,您可能会在主处理程序代码中遇到控制流错误。
After calling callback(error)
in the catch, the non-error path in the code is left hanging and fails.在 catch 中调用callback(error)
后,代码中的非错误路径挂起并失败。 Failing because the result is undefined after the catch.失败,因为结果在捕获后未定义。
Jest/node will report this as an unresolved promise error and warn you that: Jest/node 会将此报告为未解决的 promise 错误并警告您:
In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.将来,未处理的 promise 拒绝将使用非零退出代码终止 Node.js 进程。
My advice would be that if you're going to await
the api call, then instead of using .catch
, put a try...catch
around it.我的建议是,如果您要await
api 调用,那么不要使用.catch
,而是使用try...catch
。
Eg例如
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);
}
Or alternatively get rid of the await
and use it like a promise.或者摆脱await
并像 promise 一样使用它。
Eg例如
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);
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.