So I have a route with a database call in a separate file:
module.exports = (req, res) => {
const sql = `SELECT Topic FROM surveys WHERE ID="${req.params.id}"`;
model.connection.query(sql, (err, result) => {
if (err) res.status(500).send(err);
res.send(result);
});
};
In my test file, I create fake req and res objects to test different inputs:
const req = {
body: {},
params: {
id: '',
}
};
const res = {
message: '',
stat: 200,
send(arg) {
this.message = arg;
},
status(number) {
this.stat = number;
return this;
}
};
And call it in every test:
it('Should return status 200 if parameter is a number string', () => {
req.params.id = '1';
getTopic(req, res);
chai.expect(res.stat).to.equal(200);
});
My question is, how can I test it asynchronously, without affecting my route?
I've seen Mocha documentation explaining this, but they require a callback in a function.
it('Should return status 200 if parameter is a number string', (done) => {
req.params.id = '1';
getTopic(req, res); // Cannot give any callback
chai.expect(res.stat).to.equal(200);
done(); //Doesn't work
});
I also tried using async/await, but it didn't work (I lack knowledge of how to use it, maybe that's why)
it('Should return status 200 if parameter is a number string', async () => {
req.params.id = '1';
await getTopic(req, res);
chai.expect(res.stat).to.equal(200);
});
And I've seen this question, and didn't help at all: unit testing express route with async callback
Related
Right now i have this code
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
Posts.list().then(data=> {
var jsonOutput=JSON.stringify(data)
postData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Events.list().then(data=> {
var jsonOutput=JSON.stringify(data)
eventData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Messages.list().then(data=> {
var jsonOutput=JSON.stringify(data)
messageData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Users.list().then(data=> {
var jsonOutput=JSON.stringify(data)
userData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
//Then when all data from colections is retrieve i want to use the 4 variables that i created in the beggining
});
So basicly im trying to retrieve the data from my mongo database and then assign the results to that 4 variables that i create, but im not getting success.
For what i´ve been seeing i have to use async but im having some trouble doing it.
I don't like too much mrlanlee solution. This is a typical situation where using async / await can really make sense. Anyway, the Hugo's solution (the second one, with async await), even if it just works, will make the four queries in sequence, one after another to. If you want a clean, working and parallel solution, check this:
router.get('/export', async function(req, res, next) {
let data
try {
data = await Promise.all([
Posts.list(),
Events.list(),
Messages.list(),
Users.list()
]);
// at this point, data is an array. data[0] = Posts.list result, data[1] = Events.list result etc..
res.status(200).json(data)
} catch (e) {
res.status(500).send('error');
}
});
The other answer from Sashi is on the right track but you will probably run into errors. Since your catch statement on each promise returns 500, if multiple errors are caught during the query, Express will not send an error or 500 each time, instead it will throw an error trying to.
See below.
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
try {
postData = Posts.list().then(data=> {
return JSON.stringify(data);
});
eventData = Events.list().then(data=> {
return JSON.stringify(data)
});
messageData = Messages.list().then(data=> {
return JSON.stringify(data);
})
userData = Users.list().then(data=> {
return JSON.stringify(data)
});
} catch (err) {
// this should catch your errors on all 4 promises above
return res.status(500).send('error')
}
// this part is optional, i wasn't sure if you were planning
// on returning all the data back in an object
const response = {
postData,
eventData,
messageData,
userData,
};
return res.status(200).send({ response })
});
For explanation of why you weren't able to mutate the variables, see Sashi's answer as he explains it.
The variables defined outside the async code is out of scope of the async functions. Hence you cannot store the returned value from the async functions in those variables.
This should work.
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
postData = Posts.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
eventData = Events.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
messageData = Messages.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
userData = Users.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
});
Using Async/Await is a much neater solution.
router.get('/export', async function(req, res, next) {
var postData, eventData, messageData, userData;
try{
postData = await Posts.list();
eventData = await Events.list();
messageData = await Messages.list()
userData = await Users.list();
catch (e){
res.status(500).send('error');
}
});
I need to test if my POST request to my endpoint works properly with a Jest test. I had the idea of first getting the count of my Services table (I'm using sequelize orm), then to send a new post request and to finally get the new count and compare if the old count + 1 will equal to the new count, if true then the POST request works just fine.
test('Create a valid Service', async (done) => {
const service = {
name: "cool",
description: "description"
};
await Service.count().then(async function (count) {
await request(app)
.post('/api/services')
.send(service)
.then(async () => {
await Service.count().then(function (newcount) {
expect(newcount).toBe(count + 1);
});
})
.catch(err => console.log(`Error ${err}`));
});
});
For me the test looks fine, but when I run it I get:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
Is something missing or is there even a better way to test a POST request? with Jest?
It is because you are not calling the done callback passed in jest callback function. It can be done like this.
test('Create a valid Service', async(done) => {
const service = {
name: "cool",
description: "description"
};
await Service.count().then(async function (count) {
await request(app)
.post('/api/services')
.send(service)
.then(async() => {
await Service.count().then(function (newcount) {
expect(newcount).toBe(count + 1);
// execute done callback here
done();
});
})
.catch(err => {
// write test for failure here
console.log(`Error ${err}`)
done()
});
});
});
You can also write this code in this way so that the readability can be improved and maximize the use of async/await.
test('Create a valid Service', async(done) => {
const service = {
name: "cool",
description: "description"
};
try {
const count = await Service.count();
await request(app).post('/api/services').send(service)
const newCount = await Service.count()
expect(newCount).toBe(count + 1);
done()
} catch (err) {
// write test for failure here
console.log(`Error ${err}`)
done()
}
});
By default Jest also resolves the promise in async/await case. We can achieve this without the callback function also
test('Create a valid Service', async() => {
const service = {
name: "cool",
description: "description"
};
try {
const count = await Service.count();
await request(app).post('/api/services').send(service)
const newCount = await Service.count()
expect(newCount).toBe(count + 1);
} catch (err) {
// write test for failure here
console.log(`Error ${err}`)
}
});
I'm doing a POST to create an item and send the newly created item as response back to the client.
async (req, res, next) => {
const item = await createItem(xx, yy, zz);
res.send(201, item);
}
Now I also want to send out notifications after creating an item but also after responding to the client - to make the request as fast as possible.
async (req, res, next) => {
const item = await createItem(xx, yy, zz);
res.send(201, item);
sendNotification(item);
}
If I want to test this using jest + supertest, this is how it'd look:
test('return 201', () => {
const app = require('./');
return request(app)
.post('/api/items')
.send({})
.expect(201)
.then(response => {
// test something more
});
}
But how could I test if the sendNotification() was called?
Ok, not perfect but working right now:
I added a call to an external method from another package at the end of the async request-handler. I know that you shouldn't add code just for testing purposes but I prefered this to random setTimeouts in my tests.
hooks.js
const deferreds = [];
exports.hookIntoEnd = () => {
const p = new Promise((resolve, reject) => {
deferreds.push({ resolve, reject });
});
return p;
};
exports.triggerEndHook = () => {
if (Array.isArray(deferreds)) {
deferreds.forEach(d => d.resolve());
}
};
handler.js
const { triggerEndHook } = require('./hooks');
async (req, res, next) => {
const item = await createItem(xx, yy, zz);
res.send(201, item);
sendNotification(item);
// this is only here so that we can hook into here from our tests
triggerEndHook();
}
test.js
test('run + test stuff after res.send', async () => {
const server = require('../index');
const { hookIntoEnd } = require('../hooks');
const aws = require('../utils/aws');
const getObjectMetadataSpy = jest
.spyOn(aws, 'getObjectMetadata')
.mockImplementation(() => Promise.resolve({ Metadata: { a: 'b' } }));
const p = hookIntoEnd();
const response = await request(server)
.post('/api/items')
.send({ foo: 'bar' })
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(201);
expect(response.body).toEqual({ id: 1, name: 'test item'});
// test for code that was run after res.send
return p.then(async () => {
console.log('>>>>>>>>>>> triggerEndHook');
expect(getObjectMetadataSpy).toHaveBeenCalledTimes(2);
});
});
You can use mocking in Jest to spy on the sendNotification() function and assert that it has been called. A simple example:
const sendNotification = require('./sendNotification');
const sendNotificationSpy = jest.spyOn(sendNotification);
test('return 201', () => {
const app = require('./');
return request(app)
.post('/api/items')
.send({})
.expect(201)
.then(response => {
// test something more
expect(sendNotificationSpy).toHaveBeenCalled();
});
}
After res.send() is called the program calls someService.method({param1}) function.
Using sinon to spy that service method:
it('test after send', function(done){
const spy = sinon.spy(someService, 'method');
agent
.set('Authorization', token)
.post('/foo')
.expect(200)
.then(() => {
return setTimeout(function() {
// Assert the method was called once
sinon.assert.callCount(spy, 1);
// Assert the method was called with '{param1}' parameter
sinon.assert.calledWith(spy, {param1});
// Test callback!
done();
}, 100);
});
});
- Using setTimeout with the minimus time (ms) as possible to wait for the method to be called.
Recommendations and improvements will be appreciated! (I'm still trying to avoid using arbitrary amount of timeout)
I am new to nodejs.
How do I stub my return result which is a callback.
I know that I should not access the DB when doing testing.
I am doing unit testing at the controller level.
Here is my flow on how I going to do my test based on my understanding from java.
Mock Request and Response.
Set param of request.
Mock bookDAO.selectBook so that it return a user defined result. Thus not calling DB.
Verify / assert the value of the return results. (i.e. Response must be 200, JSON format, must have column BOOK_ID, BOOK_TITLE, etc)
However, i was not able to successfully mock my function. After running npm test, this is the error that I am receiving.
2018-10-02T10:00:17.809 1) Book service
1. should list a SINGLE Book /book/id GET:
Error: selectBook cannot yield to '[object Object]' since no callback was passed. Received [XCV1234, function (result) {
res.status(200).json({
message: format(message.DEFAULT_MSG, "GET", constant.MODULE_URL),
result: result
});
}]
at throwYieldError (node_modules\sinon\lib\sinon\call.js:22:11)
at Object.yieldToOn (node_modules\sinon\lib\sinon\call.js:167:13)
at Object.yieldTo (node_modules\sinon\lib\sinon\call.js:156:31)
at Function.spyApi.(anonymous function) [as yieldTo] (node_modules\sinon\lib\sinon\spy.js:416:61)
at Context.it (test\controller\BookController.spec.js:47:17)
Am i doing it the right way? how do i return the callback result ?
bookController.js:
exports.getBook = (req, res) => {
//get from request
const id = req.params.id;
const params = [id];
bookDao.selectBook(params, function (result) {
res.status(200).json({
message: format(message.DEFAULT_MSG, "GET", constant.MODULE_URL),
result: result
});
});
};
bookDao.js:
function selectBook(params, callback) {
pool.open(connString, function (err, conn) {
conn.queryResult(query.SQL_SELECT, params, function (err, result) {
if (err) {
console.error(err);
return conn.closeSync();
}
var data = result.fetchAllSync();
// only when successful then call closeSync
result.closeSync();
return callback(data);
});
conn.close();
});
}
bookRest.js:
module.exports = (app) => {
// map to controller
const controller = require('../controller/bookController');
app.route(constant.MODULE_URL + '/:id').get(controller.getbook);
app.route(constant.MODULE_URL).put(controller.updateBooks);
};
bookController.spec.js:
process.env.NODE_ENV = 'test';
const sinon = require('sinon');
const chai = require('chai');
const chaiHttp = require('chai-http');
const should = chai.should();
const httpMocks = require('node-mocks-http');
let server = require('../../../main.js');
const bookController = require('../../../controller/bookController.js');
const bookDao = require('../../../dao/bookDao.js');
chai.use(chaiHttp);
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
describe('Book service', () => {
beforeEach(() => {
});
afterEach(() => {
});
it('1. should list a SINGLE Book /book/id GET', (done) => {
req.params.id = "XCV1234";
const selectbook = sinon.stub(bookDao, "selectbook");
bookController.getbook(req, res);
selectbook.yieldTo({BOOK_ID : "XCV1234"});
res.should.have.status(200);
res.should.be.json;
res.body.should.be.a('object');
res.body.result[0].should.include.keys(
'BOOK_ID'
);
sinon.restore();
done();
});
});
I'm afraid yieldsTo is not the appropriate method to use for this case. Based on documentation, this method is intended to target callback that passed as property as in
sinon.stub(jQuery, "ajax").yieldsTo("success", [1, 2, 3]);
jQuery.ajax({
success: function (data) {
assertEquals([1, 2, 3], data);
}
});
To solve your problem, we can use yields so it will be like:
...
// should be stubbed before `getbook` is called
sinon.stub(bookDao, "selectbook").yields({
BOOK_ID: "XCV1234"
});
bookController.getbook(req, res);
res.should.have.status(200);
...
Hope it helps
I'm writing unit tests for separate middleware functions in Node/Express using Jest.
A simple example of the middleware:
function sendSomeStuff(req, res, next) {
try {
const data = {'some-prop':'some-value'};
res.json(data);
next();
} catch (err) {
next(err);
}
}
And a sample of my test suite:
const httpMocks = require('node-mocks-http');
const { sendSomeStuff } = require('/some/path/to/middleware');
describe('sendSomeStuff', () => {
test('should send some stuff', () => {
const request = httpMocks.createRequest({
method: 'GET',
url: '/some/url'
});
let response = httpMocks.createResponse();
sendSomeStuff(request, response, (err) => {
expect(err).toBeFalsy();
// How to 'capture' what is sent as JSON in the function?
});
});
});
I have to provide a callback to populate the next parameter, which is called in the function. Normally, this would 'find the next matching pattern', and pass the req and res objects to that middleware. However, how can I do this in a test set-up? I need to verify the JSON from the response.
I don't want to touch the middleware itself, it should be contained in the test environment.
Am I missing something here?
Found a fix!
Leaving this here for someone else who might struggle with the same.
When returning data using res.send(), res.json() or something similar, the response object (from const response = httpMocks.createResponse();)
itself is updated. The data can be collected using res._getData():
const httpMocks = require('node-mocks-http');
const { sendSomeStuff } = require('/some/path/to/middleware');
describe('sendSomeStuff', () => {
test('should send some stuff', () => {
const request = httpMocks.createRequest({
method: 'GET',
url: '/some/url'
});
const response = httpMocks.createResponse();
sendSomeStuff(request, response, (err) => {
expect(err).toBeFalsy();
});
const { property } = JSON.parse(response._getData());
expect(property).toBe('someValue');
});
});
});
I did a different way by utilising jest.fn(). For example:
if you wanna test res.json({ status: YOUR_RETURNED_STATUS }).status(200);
const res = {};
res.json = jest.fn(resObj => ({
status: jest.fn(status => ({ res: { ...resObj, statusCode: status }
})),
}));
Basically, I mock the res chain methods(json and status).
That way you can do expect(YOUR_TEST_FUNCTION_CALL).toEqual({ res: { status: 'successful', statusCode: 200 }}); if your response structure is like that.