I'm testing an AWS Lambda handler by checking if the callback function is called.
async function ingest(event, _, callback) {
const a = someFunc(); //Can mock this out fine
callback(null, response);
};
in my tests:
it('should call the callback', async () => {
const event = { message: 'some data'};
const expectedResponse = { statusCode: 200, message: "test" };
someFunc= jest.fn().mockReturnValue(expectedResponse)
const context = {
awsRequestId: 'test-request-id',
functionName: 'test-function-name',
getRemainingTimeInMillis: () => 5000, // return a fake remaining time
};
const callback = jest.fn();
await handler(event, context, callback)
expect(callback).toHaveBeenCalledWith(null, expectedResponse);
});
However I am getting callback is not a function
EDIT: I am also using middy to parse my lambda handler:
export const handler = middy(ingest).use(
injectLambdaContext(logger, { clearState: true })
);
Apparently middy deprecated callbacks in v2. It wasnt being passed through the middleware.
Related
In the setup below, if I run the test as is, myFunc is not mocked when I debug into handler.
However, if instead I add this. in front of the myFunc call in handler, then the function is mocked and everything works as expected.
Can someone please explain why this is? I'm new to mocking and can't see it.
I know what this does, but why won't jest mock without it since I told it to mock that function in the module?
index.js
const aws = require('aws-sdk')
exports.handler = async function (event, context) {
let s;
switch (event.func) {
case "myFunc":
console.log('Executing myFunc');
//making the call: s = await this.myFunc.apply(null, [event.params]) will make the mock work.
s = await myFunc.apply(null, [event.params])
console.log(s);
return s;
/*cases...*/
default:
// default behaviour
}
async myFunc({p1, p2}){
/* do something */
return x
}
exports.myFunc = myFunc
}
index.spec.js
jest.mock('./index.js', () => {
const allAutoMocked = jest.createMockFromModule('./index.js')
const actual = jest.requireActual('./index.js')
return {
__esModules: true,
...allAutoMocked,
myFunc : jest.fn().mockImplementation(() => ({ mockedValue: 'test' })),
handler: actual.handler
}
})
let index = require("./index.js")
describe('Test myFunc', () => {
test('If myFunc function was called', async () => {
var event = { func: 'myFunc', params: { p1: xx, p2: false } };
const context = {};
const logMock = jest.fn((...args) => console.log(...args));
const data = await handler(event, context);
})
})
I am using Auth0 to perform authentication in my React app.
Upon the default reroute that Auth0 performs after getting the information, I need to parse the hash that it returns then use some of the returned into to save the auth Token to the store for other tasks later. However this function that handles storing of the authToken occurs in the callback of parseHash (an auth0 function).
How can I wait for handleLogin() (the function being called in the callback) to complete before moving on with other tasks? I cannot make parseHash() async as I don't have real access to it.
Root.tsx
`if (this.props.location.hash) {
this.props.authClient.parseHash(
{ hash: this.props.location.hash },
(err, authResult) => handleLogin(err, authResult, this.props.dispatch)
);
}
}`
handleLogin.ts
`export const handleLogin = (
err: Auth0ParseHashError | null,
authResult: Auth0DecodedHash | null,
dispatch: Dispatch
) => {
if (authResult) {
const userId = authResult.idTokenPayload.sub;
dispatch(
setAuthToken({
token: {
accessToken: authResult.idToken,
userId
}
})
);
}
};`
This is information that is supplied about parseHash() from Auth0
`parseHash(
options: ParseHashOptions,
callback: Auth0Callback<Auth0DecodedHash | null, Auth0ParseHashError>
): void;`
`Decodes the id_token and verifies the nonce.
#param callback: function(err, {payload, transaction})`
If you are using redux thunk then the dispatch will be returning a promise. You should be able to use .then for example
handleLogin(err, authResult, this.props.dispatch).then(() => {/*other task code here*/})
You'll also need to return the dispatch from your handleLogin function
const test = async () => {
await example();
console.log(`finished !`);
}
function example() {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 2000);
});
}
test();
Then for your case I would say :
Inside an async function
// Async anonymous function that directly executes
(async() => {
const ret = await (err, authResult) => handleLogin(err, authResult, this.props.dispatch);
if (this.props.location.hash) {
this.props.authClient.parseHash(
{ hash: this.props.location.hash },
ret
);
}
})()
The thing is, handleLogin() does not seem to return anything, so ret might stay null. Maybe there is a problem with your logic.
Remember that:
(x) => x
is the same as:
(x) => { return x; }
, with arrow functions.
I'm using TypeScript to write a very simple service that utilizes the AWS SDK. My Jest unit tests are passing, but the coverage reports are saying that the line 'return result.Items' is not covered. Can anyone tell why this is? Is it a bug in jest?
// service file
/**
* Gets an array of documents.
*/
function list(tableName) {
const params = {
TableName: tableName,
};
return docClient
.scan(params)
.promise()
.then((result) => {
return result.Items;
});
}
// test file
const stubAwsRequestWithFakeArrayReturn = () => {
return {
promise: () => {
return { then: () => ({ Items: 'fake-value' }) };
},
};
};
it(`should call docClient.scan() at least once`, () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
db.list('fake-table');
expect(mockAwsCall).toBeCalledTimes(1);
});
it(`should call docClient.scan() with the proper params`, () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
db.list('fake-table');
expect(mockAwsCall).toBeCalledWith({
TableName: 'fake-table',
});
});
it('should return result.Items out of result', async () => {
const mockAwsCall = jest
.fn()
.mockImplementation(stubAwsRequestWithFakeArrayReturn);
aws.docClient.get = mockAwsCall;
const returnValue = await db.get('fake-table', 'fake-id');
expect(returnValue).toEqual({ Items: 'fake-value' });
});
The line not covered is the success callback passed to then.
Your mock replaces then with a function that doesn't accept any parameters and just returns an object. The callback from your code is passed to the then mock during the test but it doesn't call the callback so Jest correctly reports that the callback is not covered by your tests.
Instead of trying to return a mock object that looks like a Promise, just return an actual resolved Promise from your mock:
const stubAwsRequestWithFakeArrayReturn = () => ({
promise: () => Promise.resolve({ Items: 'fake-value' })
});
...that way then will still be the actual Promise.prototype.then and your callback will be called as expected.
You should also await the returned Promise to ensure that the callback has been called before the test completes:
it(`should call docClient.scan() at least once`, async () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
await db.list('fake-table'); // await the Promise
expect(mockAwsCall).toBeCalledTimes(1);
});
it(`should call docClient.scan() with the proper params`, async () => {
const mockAwsCall = jest.fn().mockImplementation(stubAwsRequest);
aws.docClient.scan = mockAwsCall;
await db.list('fake-table'); // await the Promise
expect(mockAwsCall).toBeCalledWith({
TableName: 'fake-table',
});
});
The Library chai-as-promised is worth looking at.
https://www.chaijs.com/plugins/chai-as-promised/
Instead of manually wiring up your expectations to a promise’s
fulfilled and rejected handlers.
doSomethingAsync().then(
function (result) {
result.should.equal("foo");
done();
},
function (err) {
done(err);
}
);
you can write code that expresses what you really mean:
return doSomethingAsync().should.eventually.equal("foo");
I have an async lambda, which performs an async SQS sendMessage request. The SQS queue is a standard queue, not FIFO, just to clarify.
Here's an example of code (without irrelevant part of the logic):
exports.functionHandler = async (event, context, callback) => {
try {
let parsedBody = JSON.parse(event.Records[0].body);
let modifiedBody = await doStuff(parsedBody);
let sqsPayload = {
MessageBody: JSON.stringify(modifiedBody),
QueueUrl: my-queue-url
};
await sqs.sendMessage(sqsPayload).promise();
callback(null, utils.respondSuccess("Done"));
} catch (err) {
// Handle error
callback(null, utils.respondError(err));
}
};
const doStuff = async payload => {
// Do stuff
}
Pretty simple.
Now the problem: I'm trying to test this function using the package aws-sdk-mock. This is how I was stubbing the sendMessage function when the lambda wasn't async and the sendMessage function was using the callback:
it("an awesome title for my test", async () => {
let payload = {
Records: [
// Data here
]
};
AWS.mock("SQS", "sendMessage", (param, callback) => {
let response = {
ResponseMetadata: {
RequestId: "test-request-id"
},
MD5OfMessageBody: "a892e8d8589e97ca92fb70020f01c16c",
MessageId: "test-message-id"
};
callback(null, response);
});
await app.functionHandler(payload, {}, (err, result) => {
let parsedBody = JSON.parse(result.body);
expect(parsedBody.message).to.be.equal("Done");
// More stuff
});
AWS.restore();
});
If I use this test, the sendMessage function throws the following error:
sendMessage returned an invalid MD5 response. Got "undefined", expecting "a892e8d8589e97ca92fb70020f01c16c".
I'm not sure how to test sendMessage asynchronously. I don't mind adopting a different package if it helps me to get the job done.
Can anyone help?
Thanks a lot
I've not used aws-sdk-mock but apparently in your mock you are using callback and in the lambda handler it is an async call. I use proxyquire for mocking dependencies. Here is an example:
functionHandler.js
Don't need to use callback and context in Lambda runtime Node8.10.
let AWSSQS = require('aws-sdk/clients/sqs');
let sqs = new AWSSQS();
exports.functionHandler = async (event) => {
// No need to use callback when Lambda runtime is 8.10.
try {
let parsedBody = JSON.parse(event.Records[0].body);
let modifiedBody = await doStuff(parsedBody);
let sqsPayload = {
MessageBody: JSON.stringify(modifiedBody),
QueueUrl: my-queue-url
};
await sqs.sendMessage(sqsPayload).promise();
return utils.respondSuccess('Done');
} catch (err) {
throw utils.respondError(err);
}
};
test.spec.js
Pretty much self explanatory. Your define an object with name of dependency as property.
const proxyquire = require('proxyquire');
let app = require('path/to/function');
describe('SQS', () => {
it("an awesome title for my test", async (done) => {
const app = proxyquire(app, {
'aws-sdk/clients/sqs': function() {
this.sendMessage = (params) => {
return {
promise: () => {
return Promise.resolve({
ResponseMetadata: {
RequestId: 'test-request-id'
},
MD5OfMessageBody: 'a892e8d8589e97ca92fb70020f01c16c',
MessageId: 'test-message-id'
});
}
}
}
}
});
let payload = {
Records: [
// Data here
]
};
const data = await app.functionHandler(payload);
let parsedBody = JSON.parse(data.body);
expect(parsedBody.message).to.be.equal("Done");
done();
});
});
I'm trying to write dome mocks for my async lambda but I'm getting an error 'TypeError: Cannot stub non-existent own property account'. Any ideas how to get around it? thanks in advance.
---- test
import { handler } from 'handler'
describe('tests', function() {
const sbox = sandbox.create()
describe('a payload is sent', () => {
it('should return a 200', (done) => {
const thePayload = thedata;
sbox.stub(handler, 'handler').resolves(thePayload)
account(context, context, (err, resp) => {
expect(resp.statusCode).to.eq(200)
done()
})
})
})
})
---- handler
export const handler = async (event, context, callback) => {
return callback(null, { statusCode: 200 } )
}