Background
I am trying to learn how to do a RESTful API following the TDD paradigm by reading a book on the subject (it is in brazillian):
https://leanpub.com/construindo-apis-testaveis-com-nodejs/read
The author encourages the use a sinon.js together with mocha.js.
I am getting close to the end, but I am failling to pass the test for my gnomeController.
Problem
The problem is that I am using sinon to assert that I am calling the gnomeController's get method using the given reponse object, which is in reallity a spy.
This spy is to make sure I call the reponse method with an "Error", but it appears I am calling the response with no arguments whatsoever, which is very confusing.
Code
gnomeController.js
module.exports = aGnomeModel => {
let Gnome = aGnomeModel;
function get(req, res){
return Gnome.find({})
.then(gnomes => res.send(gnomes))
.catch(err => res.status(400).send(err));
}
return Object.freeze({
get
});
};
gnomeTest.js
const sinon = require("sinon");
const gnomesControllerFactory = require("gnomesController.js");
const Gnome = require("gnomeModel.js");
describe("Controllers: Gnomes", () => {
describe("get() gnomes", () => {
it("should return 400 when an error occurs", () => {
const request = {};
const response = {
send: sinon.spy(),
status: sinon.stub()
};
response.status.withArgs(400).returns(response);
Gnome.find = sinon.stub();
Gnome.find.withArgs({}).rejects("Error");
const gnomesController = gnomesControllerFactory(Gnome);
return gnomesController.get(request, response)
.then(arg => {
console.log(arg);
sinon.assert.calledWith(response.send, "Error");
});
});
});
});
Question
I am using the latest versions of both libraries.
What is wrong in my code, why is the reponse being called with no arguments?
Solution
After much debugging, I found out that the solution is to replace:
function get(req, res){
return Gnome.find({})
.then(gnomes => res.send(gnomes))
.catch(err => res.status(400).send(err));
}
with:
function get(req, res){
return Gnome.find({})
.then(gnomes => res.send(gnomes))
.catch(err => res.status(400).send(err.name));
}
Which is not explained in the book. Kinda wish I could give more feedback on it, but so far it is what it is.
Related
I have the following jest test configuration for my collection of AWS JS Node Lambdas. I have a module called dynamoStore I reference in several different lambdas package.json and use within the lambdas. I am trying to get test one of these lambdas by mocking the dynamo store module as it makes calls to dynamoDb. The problem is that the jest.fn implementation never gets called. I confirmed this by sticking a breakpoint in that line as well as logging the value the calling methods returns from it.
When I check lambda1/index.js in the debugger getVehicleMetaKeysFromDeviceId() is a jest object but when it is called it doesn't use my mock implementation
How do I get this implementation to work? Have I set up my mock incorrectly?
dynamoStore/vehicleMetaConstraints
exports.getVehicleMetaKeysFromDeviceId= async (data) => {
return data
};
dynamoStore/index.js
exports.vehicleMetaConstraints = require("./vehicleMetaConstraints");
...
lambda1/index.js
const { vehicleMetaStore } = require("dynamo-store");
exports.handler = async (event, context, callback) => {
const message = event;
let vehicle_ids = await vehicleMetaStore.getVehicleMetaKeysFromDeviceId(message.id);
// vehicle_ids end up undefined when running the test
}
lambda1/index.test.js
const { vehicleMetaStore } = require("dynamo-store");
jest.mock("dynamo-store", () => {
return {
vehicleMetaStore: {
getVehicleMetaKeysFromDeviceId: jest.fn(),
},
};
});
describe("VehicleStorageLambda", () => {
beforeEach(() => {
jest.resetModules();
process.env = { ...env };
});
afterEach(() => {
jest.clearAllMocks();
});
test("Handles first time publish with existing device", async () => {
let functionHandler = require("./index");
vehicleMetaStore.getVehicleMetaKeysFromDeviceId.mockImplementationOnce(() =>
// This never gets called
Promise.resolve({
device_id: "333936303238510e00210022",
})
);
await functionHandler.handler({});
});
});
Remove the call to jest.resetModules() in beforeEach. That's re-importing your modules before each test, and wiping out your mocks.
https://stackoverflow.com/a/59792748/3084820
I want to test a route that makes external api calls.
I would like to stub the functionThatShouldBeStubbed so I can skip the external api call and focus on testing the route instead.
I am using Sinon and rewire, because if I understood correctly I cannot stub a function that was exported the way it currently is.
However, it seems like even though rewire replaced the function, my test is still making external api call. It seems like sinon is not aware that the function was rewired. How can I make this situation work?
//--------------------------
//../target.js
const functionThatShouldBeStubbed = async () => {
const results = await external_API_call();
return results;
}
module.exports = {
functionThatShouldBeStubbed,
/*more other functions*/
}
//--------------------------
//../index.js
app.use(require('endpoint.js'));
//--------------------------
//endpoint.js
const { functionThatShouldBeStubbed } = require("target.js");
router.post('endpoint', async(req, res) => {
//do lots of stuff
const results = await functionThatShouldBeStubbed();
if(results.error) { return res.status(207).send({ /*stuff */})}
//...more stuff
})
//--------------------------
//test.js
const server = require("../index.js");
const rewire = require('rewire')
const restoreTarget = rewire('../target.js');
describe("Should return appropriate error code to requester", function () {
it("Should return 207 in this case", function (done) {
const targetStub = sinon.stub().resolves({msg: 'fake results', statusCode: 207})
const targetRewired = restoreTarget.__set__("functionThatShouldBeStubbed", targetStub);
chai.request(server)
.post("/endpoint")
.send('stuff over')
.catch((error) => {
console.log("Error: ", error)
done();
})
.then((res) => {
expect(targetStub.callCount).to.equal(1);
res.should.have.status(207);
restoreTarget();
targetStub.restore();
done();
})
})
})
Many thanks!
Edit: updated code for more detail
Edit2: updated code again to show import method
You shouldn't need rewire at all here based on how your module is being exported. The following should work
//test.js
const target = require ("../target");
const server = require("../index");
describe("Should return appropriate error code to requester", () => {
it("Should return 207 in this case", done => {
const targetStub = sinon
.stub(target, "functionThatShouldBeStubbed")
.resolves({msg: 'fake results', statusCode: 207})
chai.request(server)
.post("/endpoint")
.send('stuff over')
.then(res => {
expect(targetStub.callCount).to.equal(1);
res.should.have.status(207);
targetStub.restore();
done();
})
})
})
I want to mock the below line of code. And please explain how I can mock this in detail as I'm new to javascript and writing test cases. The below code would return a promise.
const createPriceConfiguration = (fastify, req) => {
return fastify.pg.transact(client => insertQuery(fastify, client, req));
};
const client = {
query: jest.fn(() => {
return new Promise(resolve => {
resolve({ rows: [req.body] });
});
})
};
My colleague gave a solution which I'm not able to understand.
transact: jest.fn(queryFunction => {
return queryFunction(client);
})
You want to test createPriceConfiguration function which takes fastify object and calls function from it. Mocking this function can be done by mocking the fastify object. You need to mock the transact method in the fastify object passed to return desired response (e.g. promise or result of an other function, ...)
const mockedFastify = {
pg: {
transact: jest.fn(() => new Promise(resolve => {
...desired code
}))
}
};
Then in test case You pass mocked object createPriceConfiguration(mockedFastify);
I'm writing some tests for a simple node.js module which returns the result of another function, currently that module looks like this:
const doAPostRequest = require('./doAPostRequest.js').doAPostRequest
const normalizeResponse = require('./normalizeResponse.js').normalizeResponse
const errorObject = require('./responseObjects.js').errorObject
const successObject = require('./responseObjects.js').successObject
exports.handlePost = (postData) => {
return doAPostRequest(postData).then((res) => {
const normalizedResponse = normalizeResponse(res);
return successObject(normalizedResponse);
}).catch((error) => {
const { statusCode, message } = error;
return errorObject(statusCode, message);
});
}
doAPostRequest is another module that returns a promise. Now i want to test for the following cases:
if doAPostRequest is being called once with the params given to handlePost.
is normalizeResponse is being called once with the params that are returned from doAPostRequest.
is successObject is being called once with the params that are returned from normalizedResponse.
is errorObject is being called once with the params that are returned from doAPostRequest (if there is an error).
But i can't figure out how i can test if the functions are called within the promise and if doAPostRequest is being called in the first place.
Currently i have the following test suite for the module written with the help of the Chai, Sinon and Sinon-chai libraries.
const sinon = require('sinon');
const chai = require('chai');
const handlePost = require('./handlePost.js').handlePost
const params = { body: 'title': 'foo' }
chai.use(require('sinon-chai'));
it('Calls doAPostRequest', () => {
doAPostRequest = sinon.stub().resolves("Item inserted")
handlePost(params)
expect(doAPostRequest).to.have.been.calledOnce;
expect(doAPostRequest).to.have.been.calledWith(params);
});
it('Calls normalizeResponse', () => {
normalizeResponse = sinon.stub().resolves("result")
return handlePost(params).then((res) => {
expect(normalizeResponse).to.have.been.calledOnce;
expect(normalizeResponse).to.have.been.calledWith("Item inserted");
});
});
However the tests fail with the following error response:
AssertionError: expected stub to have been called exactly once, but it was called 0 times
Maybe i'm overlooking something and is this not the right way to test this piece of code? I found this topic and the problem is close to identical, however for me non of the functions are getting called apparently. This leads to me thinking that i must overlook something. Any help and/or suggestions will be appreciated.
my jest is not working as I expect it. See:
const res = {
send: (content) => {
expect(content).toEqual({
app_status: 501,
errors: {
jwt: {
location: 'body',
param: 'jwt',
value: undefined,
msg: 'The jwt is required'
}
}
});
console.log("after expect");
done();
},
};
Basically EVERYTHING after the expect(content).toEqual ... in res.send is not called. I find that very confusing. I am getting no error except for that my test's are taking too long (because done) is not called and the test is not "closed". So my question is, am I missing something obviously?
The following should work fine. I added asynchronous since send may be called asynchronously:
const createResponse = () => {
var resolve;
const p = new Promise(
(r,reject)=>resolve=r
);
return [
{
send: (value) => {
resolve(value)
}
},
p
];
};
test('(async) fail', done => {
//Router
const router = express.Router();
//Endpoint to fetch version
router.get('/api/version', (req, res) => {
setTimeout(x=>res.send('v1'),10)
});
const request = {
method: 'GET'
};
let [response,p] = createResponse()
router.stack[0].handle(request, response, () => {});
p.then(
x=>expect(x).toBe('v2'),
reject=>expect("should not reject").toBe(reject)
).then(
x=>done()
//,x=>done() //this will cause all tests to pass
);
});
To answer your question in the comment; consider the following code:
const express = require('express');
const router = express.Router();
//Endpoint to fetch version
router.get('/api/version', (req, res) => {
res.send("Hello World");
});
const request = {
method: 'GET'
};
const response = {
send: (value) => {
debugger;//pause here and see the stack
throw new Error("Hello Error.");
}
};
router.stack[0].handle(
request,
response,
//this is the function done in route.js:
// https://github.com/expressjs/express/blob/master/lib/router/route.js#L127
err => {
console.error(err.message);//console errors Hello Error.
}
);
Send throws an error but send is called by your mock which is called by express here. So express catches the exception and then ends up here (done is not from just but your callback).
So it'll call your callback with an error, skipping done from jist and not throwing anything (maybe showing something in log). Since your callback doesn't do anything it times out.
You could try to call done from jist in the callback (at console.error(err.message);).
[UPDATE]
Careful trying to catch the error thrown by expect the following will tell me 1 test passed:
test('(async) fail', done => {
try{
expect(true).toBe(false);
}catch(e){
}
done();
});