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 } )
}
Related
I have some dependency in the tested module.
sendResponse.js:
module.exports = function sendResponse(res, data) {
res.send(data);
};
testedModule.js:
const login = require('.../sendResponse');
exports.postLogin = withServerErrorHandler(async (req, res) => {
const { body } = req;
const { email, password } = body;
const user = await login(email, password);
return sendResponse(res, user);
});
And I want to mock sendResponse module and count its calls.
const testedModule = require('.../testedModule');
jest.mock('.../sendResponse', () => jest.fn());
const sendResponse = require('.../sendResponse');
describe('testedModule', () => {
const res = {
//..
};
const req = {
//..
};
describe('Authentication', () => {
test('should pass', async (done) => {
await testedModule.postLogin(req, res);
expect(sendResponse).toHaveBeenCalledTimes(1);
done();
});
})
});
Unfortunately, Jest always gives me that result, It looks like Jest jest spy on jest.fn() function, not specific ref. How can I deal with it?
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
I want to unit test the exported method in the code below. Trying to write unit tests for a function that is downloading a zip file from a localhost server.I will write my function bellow so you understand better:
export const downloadCdn = async (cdnUrl, out) => {
const download = (resolve, reject) => {
const req = request({
method: 'GET',
uri: cdnUrl
});
req.on('response', (data) => {
// do something
});
req.on('error', (data) => {
// do something
});
req.on('data', (chunk) => {
// do something
});
req.on('end', () => {
console.log('download done');
});
req.pipe(out);
out.on('close', () => {
resolve([null, 'done']);
});
};
const downloadSummary = new Promise(download);
return downloadSummary
.then(() => [null, 'Done'])
.catch(err => [err, null]);
};
Here are my test file, what I'm trying to achieve is to have unit test that validates the download of the zip file:
import request from 'request';
import * as Module from './downloadCdn';
jest.mock('request', () => {
const mockRequest = {
pipe: jest.fn(),
on: jest.fn(),
};
return function () {
return mockRequest;
};
});
describe('Downloading a file', () => {
it('Should find the module', () => {
expect(typeof Module.downloadCdn === 'function').toBeTruthy();
});
it('Should download the zip', async () => {
const [error, response] = await Module.downloadCdn(cdnUrl, out);
expect(response === 'Done').toBeTruthy();
expect(error === null).toBeTruthy();
});
});
The response from the Promise, I receive inside the test is null, no error catching. Here is the error received from jest:
expect(received).toBeTruthy()
Expected value to be truthy, instead received false
While mocking the request, you should resolve the promise. I think that promise is not resolving that's why it's not working. I hope that the below code will be fixed your problem.
jest.mock('request', () => {
const mockRequest = {
pipe: jest.fn(),
on: (parameter, callback) => {
callback();
},
};
return function () {
return mockRequest;
};
});
Problem
I have an Action which awaits an API function. The happy path in the try is easily testable with my mocked API. However, unsure as to the best way to test and cover the .catch.
Actions
import {getRoles} from '../shared/services/api';
export const Actions = {
SET_ROLES: 'SET_ROLES'
};
export const fetchRoles = () => async dispatch => {
try {
const response = await getRoles();
const roles = response.data;
dispatch({
type: Actions.SET_ROLES,
roles
});
} catch (error) {
dispatch({
type: Actions.SET_ROLES,
roles: []
});
}
};
Actions Test
import {fetchRoles} from '../party-actions';
import rolesJson from '../../shared/services/__mocks__/roles.json';
jest.mock('../../shared/services/api');
describe('Roles Actions', () => {
it('should set roles when getRoles() res returns', async () => {
const mockDispatch = jest.fn();
await fetchRoles()(mockDispatch);
try {
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: rolesJson
});
} catch (e) {
// console.log('fetchRoles error: ', e)
}
});
// Here is the problem test, how do we intentionally cause
// getRoles() inside of fetchRoles() to throw an error?
it('should return empty roles if error', async () => {
const mockDispatch = jest.fn();
await fetchRoles('throwError')(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: []
});
});
});
Mocked API
import rolesJson from './roles.json';
export const getRoles = async test => {
let mockGetRoles;
if (test === 'throwError') {
// console.log('sad')
mockGetRoles = () => {
return Promise.reject({
roles: []
});
};
} else {
// console.log('happy')
mockGetRoles = () => {
return Promise.resolve({
roles: rolesJson
});
};
}
try {
const roles = mockGetRoles();
// console.log('api mocks roles', roles);
return roles;
} catch (err) {
return 'the error';
}
};
^ Above you can see what I tried, which did work, but it required me to change my code in a way that fit the test, but not the actual logic of the app.
For instance, for this test to pass, I have to pass in a variable through the real code (see x):
export const fetchRoles = (x) => async dispatch => {
try {
const response = await getRoles(x);
const roles = response.data;
How can we force getRoles in our mock to throw an error in our sad path, .catch test?
You can mock getRoles API on per-test basis instead:
// getRoles will be just jest.fn() stub
import {getRoles} from '../../shared/services/api';
import rolesJson from '../../shared/services/__mocks__/roles.json';
// without __mocks__/api.js it will mock each exported function as jest.fn();
jest.mock('../../shared/services/api');
it('sets something if loaded successfully', async ()=> {
getRoles.mockReturnValue(Promise.resolve(rolesJson));
dispatch(fetchRoles());
await Promise.resolve(); // so mocked API Promise could resolve
expect(someSelector(store)).toEqual(...);
});
it('sets something else on error', async () => {
getRoles.mockReturnValue(Promise.reject(someErrorObject));
dispatch(fetchRoles());
await Promise.resolve();
expect(someSelector(store)).toEqual(someErrornessState);
})
I also propose you concentrate on store state after a call not a list of actions dispatched. Why? Because actually we don't care what actions in what order has been dispatched while we get store with data expected, right?
But sure, you still could assert against dispatch calls. The main point: don't mock result returned in __mocks__ automocks but do that on peer-basis.
I resolved the test and got the line coverage for the .catch by adding a function called mockGetRolesError in the mock api file:
Thanks to #skyboyer for the idea to have a method on the mocked file.
import {getRoles} from '../shared/services/api';
export const Actions = {
SET_ROLES: 'SET_ROLES'
};
export const fetchRoles = () => async dispatch => {
try {
const response = await getRoles();
const roles = response.data;
// console.log('ACTION roles:', roles);
dispatch({
type: Actions.SET_ROLES,
roles
});
} catch (error) {
dispatch({
type: Actions.SET_ROLES,
roles: []
});
}
};
Now in the test for the sad path, I just have to call mockGetRolesError to set the internal state of the mocked api to be in a return error mode.
import {fetchRoles} from '../party-actions';
import rolesJson from '../../shared/services/__mocks__/roles.json';
import {mockGetRolesError} from '../../shared/services/api';
jest.mock('../../shared/services/api');
describe('Roles Actions', () => {
it('should set roles when getRoles() res returns', async () => {
const mockDispatch = jest.fn();
try {
await fetchRoles()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: rolesJson
});
} catch (e) {
return e;
}
});
it('should return empty roles if error', async () => {
const mockDispatch = jest.fn();
mockGetRolesError();
await fetchRoles()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: []
});
});
});
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 a standalone test for this simple middleware function
function onlyInternal (req, res, next) {
if (!ReqHelpers.isInternal(req)) {
return res.status(HttpStatus.FORBIDDEN).send()
}
next()
}
// Expose the middleware functions
module.exports = {
onlyInternal
}
This does not work
describe('success', () => {
let req = {
get: () => {return 'x-ciitizen-token'}
}
let res = {
status: () => {
return {
send: () => {}
}
}
}
function next() {}
let spy
before(() => {
spy = sinon.spy(next)
})
after(() => {
sinon.restore()
})
it('should call next', () => {
const result = middleware.onlyInternal(req, res, next)
expect(spy.called).to.be.true <-- SPY.CALLED IS ALWAYS FALSE EVEN IF I LOG IN THE NEXT FUNCTION SO I KNOW IT'S GETTING CALLED
})
})
but this does..
describe('success', () => {
let req = {
get: () => {return 'x-ciitizen-token'}
}
let res = {
status: () => {
return {
send: () => {}
}
}
}
let next = {
next: () => {}
}
let spy
before(() => {
spy = sinon.spy(next, 'next')
})
after(() => {
sinon.restore()
})
it('should call next', () => {
const result = middleware.onlyInternal(req, res, next.next)
expect(spy.called).to.be.true
})
})
Why isn't spying on just the function working?
Sinon cannot change content of existing function, so all spies it creates are just wrappers over existing function that count calls, memoize args etc.
So, your first example is equal to this:
function next() {}
let spy = sinon.spy(next);
next(); // assuming that middleware is just calling next
// spy is not used!
Your second example, equals to this:
let next = { next: () => {} }
next.next = sinon.spy(next.next); // sinon.spy(obj, 'name') just replaces obj.name with spy on it
next.next(); // you actually call spy which in in turn calls original next.next
//spy is called. YaY
So, they key to 'spying' and 'stubbing' in sinon is that you have to use spied/stubbed function in test. Your original code just used original, non-spied function.