How to mock an Express/Node.js middleware on a post route? - node.js

I´m writing some tests with chai and mocha and i am having some troubles.
For example, in the route that i paste down here, the LOGOUT calls the isLoggedIn middleware that checks if a user exists in the session.
For example, if a do this:
it('Logout', function(done) {
chai.request(baseURL)
.post('/logout')
.end(function(err, res) {
expect(err).to.be.null;
expect(res).to.have.status(204);
done();
});
});
the test faills cause i get a 401 status code. I am new on this test stuffs. I understand that i have to use sinon to get mi test pass, but i can´t get the solution.
This is my route:
'use strict';
const express = require('express');
const createError = require('http-errors');
const router = express.Router();
const bcrypt = require('bcrypt');
const User = require('../models/User');
const {isLoggedIn} = require('../helpers/middlewares');
router.post('/logout', isLoggedIn(), (req, res, next) => {
req.session.destroy();
return res.status(204).send();
});
This is the Middleware:
'use strict';
const createError = require('http-errors');
exports.isLoggedIn = () => (req, res, next) => {
if (req.session.user) {
next();
} else {
next(createError(401));
};
};
Thank you very much!!!

In your flow problem in that express middleware initialized during run express application and after becomes unavailable for stubbing. My solution is that would init stub before run express application.
test.spec.js:
const chai = require("chai"),
sinon = require("sinon"),
chaiHttp = require("chai-http"),
initServer = require("./initTestServer"),
isLoggedInMiddleware = require("./middleware");
chai.use(chaiHttp);
const { expect } = chai;
describe("Resource: /", function() {
before(function() {
sinon.stub(isLoggedInMiddleware, "isLoggedIn").callsFake(function() {
return (req, res, next) => {
next();
};
});
this.httpServer = initServer();
});
after(function() {
this.httpServer.close();
});
describe("#POST /login", function() {
beforeEach(function() {
this.sandbox = sinon.createSandbox();
});
afterEach(function() {
this.sandbox.restore();
});
it("- should login in system and return data", async function() {
return chai
.request(this.httpServer.server)
.post("/logout")
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(204);
});
});
});
});
initTestServer.js:
const isLoggedInMiddleware = require("./middleware");
const initServer = () => {
const express = require("express");
const app = express();
app.post("/logout", isLoggedInMiddleware.isLoggedIn(), (req, res, next) => {
return res.status(204).send();
});
const server = require("http").createServer(app);
server.listen(3004);
const close = () => {
server.close();
global.console.log(`Close test server connection on ${process.env.PORT}`);
};
return { server, close };
};
module.exports = initServer;

Thank you #EduardS for then answer!!
I solved it in a similar way:
it('Logout', async function(done) {
sinon.stub(helpers, 'isLoggedIn')
helpers.isLoggedIn.callsFake((req, res, next) => {
return (req, res, next) => {
next();
};
})
app = require('../index')
chai.request(app)
.post('/api/auth/logout')
.end(function(err, res2) {
expect(res2).to.have.status(204);
helpers.isLoggedIn.restore()
})
done();
});

Related

Chai testing TypeError: Converting circular structure to JSON

I'm a new learner express.js I want to test simple post and get operations with tdd mechanism. I created the test, route, index and db files but when I try to test POST method it gives me this error.
This is my routes/task.js
const express = require('express');
const router = express.Router();
router.post("/api/task", async (req,res) => {
try {
const task = await new Task(req.body).save();
res.send(task);
} catch (error) {
res.send(error);
}
})
This is my test/task.js
let chai = require("chai");
const chaiHttp = require("chai-http");
const { send } = require("process");
let server = require("../index");
//Assertion Style
chai.should();
chai.use(chaiHttp);
describe('Tasks API', () => {
/**
* Test the POST Route
*/
describe('POST /api/task', () => {
it("It should POST a new task", () => {
const task = {task: "Wake Up"};
chai.request(server)
.post("/api/task")
.send(task)
.end((err, response) => {
response.should.have.status(201);
response.body.should.be.a('string');
response.body.should.have.property('id');
response.body.should.have.property('task');
response.body.should.have.property('task').eq("Wake Up");
response.body.length.should.be.eq(1);
done();
});
});
});
});
This is my db.js
var sqlite3 = require('sqlite3').verbose()
const DBSOURCE = "db.sqlite"
let db = new sqlite3.Database(DBSOURCE, (err) => {
if (err) {
// Cannot open database
console.error(err.message)
throw err
}else{
console.log('Connected to the SQLite database.')
db.run(`CREATE TABLE IF NOT EXISTS todo (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task text
)`,
(err) => {
if (err) {
// Table already created
console.log(err);
}
});
}
});
module.exports = db
And this is my index.js
const connection = require('./db');
const express = require('express');
const app = express();
const cors = require("cors");
const port = process.env.PORT || 8080;
app.use(express.json());
app.use(cors());
app.get('/', (req, res) => {
res.send('Hello World');
});
app.post('/api/task', (req, res) => {
res.status(201).send(req);
});
app.listen(port, () => console.log(`Listening on port ${port}...`));
module.exports = app;
The thing that I try to do is building a test case to test the post method. I think I couldn't built the correct relations the files.
Currently, just by doing a POST request to /api/task, the error will appear. That is because of these lines in index.js:
app.post('/api/task', (req, res) => {
res.status(201).send(req);
});
The req parameter is circular, hence cannot be JSON-stringified.
Solution
In routes/task.js export the router:
const express = require('express');
const router = express.Router();
router.post("/api/task", async (req,res) => {
try {
const task = await new Task(req.body).save();
res.send(task);
} catch (error) {
res.send(error);
}
})
// By adding this line you can export the router
module.exports = router
In index.js, include the routes/task.js file and pass it to app.use(...), also remove the now-obsolete /api/task route:
const connection = require('./db');
const express = require('express');
const app = express();
const cors = require("cors");
const taskRoutes = require("./routes/task")
const port = process.env.PORT || 8080;
app.use(express.json());
app.use(cors());
app.get('/', (req, res) => {
res.send('Hello World');
});
app.use(taskRoutes)
app.listen(port, () => console.log(`Listening on port ${port}...`));
module.exports = app;
This way we got rid of the circular structure stringifying and the tests should now pass.

How to stub function used on a server in mocha test suite

I'm trying to stub auth.session when testing endpoint /allowUser2 on an express server app.js.
//--auth.js--
module.exports.session = (req, res, next) => {
req.user = null;
next();
};
//--app.js--
const express = require('express');
const auth = require('./auth');
const app = express();
app.use(auth.session);
app.get('/allowUser2', (req, res) => {
if (!req.user) return res.status(401).send();
if (req.user.user === 2) return res.status(200).send();
});
app.listen(4001).on('listening', () => {
console.log(`HTTP server listening on port 4001`);
});
module.exports = app;
If I just have this one test file test1.js in my test suite, auth gets stubbed successfully.
//--test1.js--
let app;
const sinon = require('sinon');
const auth = require('../../auth.js');
const chai = require('chai');
const chaiHttp = require('chai-http');
const { expect } = chai;
chai.use(chaiHttp);
let agent;
describe('should allow access', () => {
before(async () => {
// delete require.cache[require.resolve('../../app.js')]; // causes Error: listen EADDRINUSE: address already in use
sinon.stub(auth, 'session').callsFake((req, res, next) => {
req.user = { user: 1 };
next();
});
app = require('../../app.js');
agent = chai.request.agent(app);
});
after(async () => {
auth.session.restore();
});
it('should not allow access', async function () {
const response = await agent.get('/allowUser2');
expect(response.status).to.be.equal(200);
});
});
However, if I have more than one test file that requires app.js then I have a problem. If app.js was already required in another test file, such as test2.js below, node doesn't reload app.js when it's required again in test1.js. This causes app.js to call the old auth.session function, not the new stubbed one. So the user isn't authenticated and the test fails.
//--test2.js--
const chai = require('chai');
const chaiHttp = require('chai-http');
const app = require('../../app.js');
const { expect } = chai;
chai.use(chaiHttp);
const agent = chai.request.agent(app);
describe('route /allowUser2', () => {
it("shouldn't allow access", async function () {
const response = await agent.get('/allowUser2');
expect(response.status).to.be.equal(401);
});
});
I tried to reload the app.js by using delete require.cache[require.resolve('../../app.js')];. This worked when reloading a file with a plain function, but when the file is a server like app.js this causes an error: Error: listen EADDRINUSE: address already in use.
Recreate:
download Repo
npm i
npm test
How do you stub a function on the server?
One solution is turn app.js into a function that starts the server on a port number passed in as an argument. Then change the port randomly when requiring. I do not like this option because there may be some reason to keep the app on a specific port.
app.js
const express = require('express');
const auth = require('./auth');
module.exports = (port) => {
const app = express();
app.use(auth.session);
app.get('/allowUser2', (req, res) => {
if (!req.user) return res.status(401).send();
if (req.user.user === 2) return res.status(200).send();
});
app.listen(port).on('listening', () => {
console.log(`HTTP server listening on port ${port}`);
});
return app;
};
when requiring
app = require('../../app.js')((Math.random() * 10000).toString().slice(0, 4));
Instead of exporting the app in app.js, I export a function that launches the server and returns the server instance and app. By exporting the server instance I have the ability to close the server. The app is needed to pass into chai. Make sure const app = express(); is in this function and not before it or it won't recreate.
const express = require('express');
const auth = require('./auth');
const port = 4000;
module.exports = () => {
const app = express();
app.use(auth.session);
app.get('/allowUser2', (req, res) => {
if (!req.user) return res.status(401).send();
if (req.user.user === 2) return res.status(200).send();
});
app.post('/allowUser2', (req, res) => {
if (!req.user) return res.status(401).send();
if (req.user.user === 2) return res.status(200).send();
});
return {
server: app.listen(port).on('listening', () => {
console.log(`HTTP server listening on port ${port}`);
}),
app,
};
};
Then in my tests I can launch the server in before and close the server in after in both tests.
let app;
const sinon = require('sinon');
const auth = require('../../auth.js');
const chai = require('chai');
const chaiHttp = require('chai-http');
const { expect } = chai;
chai.use(chaiHttp);
let server;
describe('route /allowUser2', () => {
before(async () => {
// delete require.cache[require.resolve('../../app.js')]; // causes an error: `Error: listen EADDRINUSE: address already in use`.
sinon.stub(auth, 'session').callsFake((req, res, next) => {
req.user = { user: 2 };
next();
});
server = require('../../app.js')();
agent = chai.request.agent(server.app);
});
after(async () => {
server.server.close(() => {
console.log('Http server closed.');
});
auth.session.restore();
});
it('should allow access', async function () {
const response = await agent.get('/allowUser2');
expect(response.status).to.be.equal(200);
});
});
const chai = require('chai');
const chaiHttp = require('chai-http');
const { expect } = chai;
chai.use(chaiHttp);
let server;
let agent;
describe('route /allowUser2', () => {
before(async () => {
server = require('../../app.js')();
agent = chai.request.agent(server.app);
});
after(async () => {
server.server.close(() => {
console.log('Http server closed.');
});
});
it("shouldn't allow access", async function () {
const response = await agent.get('/allowUser2');
expect(response.status).to.be.equal(401);
});
});
working repo
UPDATE: Proposed Solution https://github.com/DashBarkHuss/mocha_stub_server/pull/1
One problem is the way you are using a direct method reference in app.js prevents Sinon from working. https://gist.github.com/corlaez/12382f97b706c964c24c6e70b45a4991
The other problem (address in use) is because each time we want to get a reference to app, we are trying to create a server in the same port. Breaking that app/server creation into a separate step alleviates that issue.

How do we pass parameters to a mounted route in nodeJS?

I'm taking a course on NodeJS, there were a few assignments related to routing, everything works fine except this part which seems a little odd: For some reason, I cannot read the parameter ID being passed to the mounted router.
dish.js
const express = require('express');
const bodyParser = require('body-parser');
const dishRouter = express.Router();
dishRouter.use(bodyParser.json());
dishRouter.route('/')
.all((req,res,next) => {
res.statusCode = 200;
res.setHeader('Content-Type','text/plain');
next();
})
.get((req,res) => {
console.info('Info: ',req);
res.end(`Sending details of the dish back to you: ${req.params.dishId}`);
})
.post((req,res) => {
res.statusCode = 403;
res.end(`Operation not supported: ${req.params.dishId}`);
})
.put((req,res) => {
res.write(`Updating the dish...: ${req.params.dishId} \n` );
res.end(`Will update this dish: ${req.body.name} with details: ${req.body.description}`);
})
.delete((req,res) => {
res.end(`Deleting this dish: ${req.params.dishId}`);
});
exports.dish = dishRouter;
dishes.js
const express = require('express');
const bodyParser = require('body-parser');
const dishesRouter = express.Router();
dishesRouter.use(bodyParser.json());
dishesRouter.route('/')
.all((req,res,next) => {
res.statusCode = 200;
res.setHeader('Content-Type','text/plain');
next();
})
.get((req,res) => {
res.end('Sending all dishes back to you');
})
.post((req,res) => {
res.end(`Will add the dish: ${req.body.name} with details: ${req.body.description}`);
})
.put((req,res) => {
res.statusCode = 403;
res.end(`Operation not supported.`);
})
.delete((req,res) => {
res.end(`Deleting all dishes.....`);
});
exports.dishes = dishesRouter;
index.js
const express = require('express');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const http = require('http');
const dishRouter = require('./routes/dish');
const dishesRouter = require('./routes/dishes');
const hostname = 'localhost';
const port = 3000;
const app = express();
app.use(morgan('dev'));
app.use(bodyParser.json());
app.use('/dishes',dishesRouter.dishes);
app.use('/dishes/:dishId',dishRouter.dish);
app.use(express.static(__dirname+'/public'));
app.use((req,res,next) => {
res.statusCode = 200;
res.setHeader('Content-Type','text/html');
res.end('<html><body><h1>This is an Express Server</h1></body></html>');
});
const server = http.createServer(app);
server.listen(port,hostname,(req,res) => {
console.info(`Server running on port: ${port}, at: ${hostname}`);
})
This GET localhost:3000/dishes/123 is calling the right route, but the parameter dishId comes back as "undefined". Again, just learning nodeJS, seems like my receiver/mounted route should receive those parameters just fine, the body can be read properly, but not the params. ... thanks.
Yeah the params don't flow between routers. You're on a new router, hence new route params object.
You can check out the code for this:
https://github.com/expressjs/express/blob/master/lib/router/index.js#L43
Check out line 43 and line 53 where route.params is set to an empty object.
Some examples:
index.js
app.use('/dishes/:dishId',(req, res) => {
console.log('now I get my dishId', req.params.dishId)
});
dish.js (version 1)
dishRouter.route('/')
.get((req, res) => {
console.log('now i get nothing', req.params)
})
dish.js (version 2)
dishRouter.route('/:anotherId')
.get((req, res) => {
console.log('now we get another parameter', req.params.anotherId)
})
// the path would be /dish/123/456
I'm not sure if there is a offical-expressjs-way to pass the params object between routers.
One solution would be to create a custom handler
index.js
app.use('/dishes/:dishId', handler)
handler.js
function handler (req, res, next) {
if (req.method === 'GET') {
console.log('now we get it', req.params)
}
}
module.exports = handler
Anoter way would be to add the dishId to the request object before calling the router:
index.js
app.use('/dishes/:dishId', (req, res, next) => {
req.dishId = req.params.dishId
router(req, res, next)
})
dish.js
const express = require('express')
const router = express.Router()
router.route('/')
.get((req, res) => {
console.log('nothing here', req.params)
console.log('dishId', req.dishId)
})
module.exports = router
Third way would be to send the params as options to a router function
index.js
app.use('/dishes/:dishId', (req, res, next) => {
router(req.params)(req, res, next)
})
dish.js
function createRouter (options) {
const router = express.Router()
router.route('/')
.get((req, res) => {
console.log('nothing here', req.params)
console.log('but alot here', options)
})
return router
}
module.exports = createRouter
If you want you could also just put the :dishId on the router as an optional parameter
index.js
app.use('/dishes', dishesRouter)
dishes.js
const express = require('express')
const router = express.Router()
router.route('/:dishId?')
.get((req, res) => {
if (req.params.dishId) {
res.end(`Sending details of the dish back to you: ${req.params.dishId}`)
} else {
res.end('Sending all dishes back to you');
}
})
module.exports = router

Why my error is not being handled in express.js?

I am learning express.js, and I am thinking why I receive UnhandledPromiseRejectionWarning and no error response is being sent after executing http api call..
For error handling I have created helper class and middleware:
/helpers/errorHander.js
------------------------------------
class ErrorHandler extends Error {
constructor(statusCode, message) {
super();
this.statusCode = statusCode;
this.message = message;
}
}
const handleError = (err, res) => {
const { statusCode, message } = err;
res.status(statusCode).json({
status: 'error',
statusCode,
message
});
};
module.exports = {
ErrorHandler,
handleError
};
I have enabled this middleware in app.js file:
/app.js
------------------------------------
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const config = require('./config/init');
const cors = require('cors');
const { handleError, ErrorHandler } = require('./helpers/errorHandler');
//routes
const apiRoutes = require('./api');
// connect db
config.initializeDB();
// configure bodyParser
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Enable Cors
app.use(cors());
// Set Routes
app.use('/', apiRoutes);
app.get('/error', (req, res) => {
throw new ErrorHandler(500, 'Internal server error');
});
// Enable error handling middleware
app.use((err, req, res, next) => {
handleError(err, res);
});
module.exports = app;
When I perform request to the /error endpoint, it works correctly:
/error endpoint
But when performing request to the endpoint which performs operations with mongoose I am not getting desired result:
/v1/users/ endpoint
user.route.js:
const router = require('express').Router();
const userController = require('./user.controller');
router.get('/', userController.getUsers);
router.post('/', userController.createUser);
module.exports = router;
user.controller.js:
const userService = require('../../../services/user');
module.exports = {
getUsers: (req, res, next) => {
return res.json(userService.getUsers());
},
createUser: (req, res, next) => {
const username = req.body.username;
const password = req.body.password;
res.json(userService.createUser(username, password));
}
};
user.service.js:
const User = require('../models/user');
const { ErrorHandler } = require('../helpers/errorHandler');
module.exports = {
getUsers: async () => {
return User.find({});
},
createUser: async (username, password) => {
const user = new User({ username, password });
try {
await user.save();
return user;
} catch (err) {
if (err.code === 11000) {
throw new ErrorHandler(409, 'Username already exists!');
}
throw new ErrorHandler(500, 'Internal server error');
}
}
};
Console gives such a warning:
Console Output
Why it is not working as I want, and how can I make it work?
=========================================================
Update #1
As user - jfriend00 suggested, I tried to await the promise, and here my code looks like on user.controller.js:
const userService = require('../../../services/user');
module.exports = {
getUsers: (req, res, next) => {
return res.json(userService.getUsers());
},
createUser: async (req, res, next) => {
const username = req.body.username;
const password = req.body.password;
try {
let user = await userService.createUser(username, password);
} catch (err) {
next(err);
}
}
};
And Now I get the desired outcome.

koa-router 404 on node.js

platform
node: v-4.4.5 koa: v-2.0.0 koa-router: v-7.0.0
here is my code
///<reference path="../typings/tsd.d.ts" />
//导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:
var Koa = require('koa');
var router=require("koa-router")();
// 创建一个Koa对象表示web app本身:
var app = new Koa();
// parse request body:
//add url-route:
router.get('/hello/:name', (ctx, next) => {
var name = ctx.params.name;
ctx.response.body =`<h1>Hello, ${name}!</h1>`;
});
router.get('/', function *(ctx, next) {
ctx.response.body = '<h1>Index</h1>';
});
router.all('/login', function *() {
this.redirect('/');
this.status = 301;
});
app.use(function (ctx,next){
this.body = `Invalid URL!!!${ctx.request.method} ${ctx.request.url}`;
ctx.response.type = 'text/html';
ctx.response.body = this.body;
});
app.use(router.routes())
.use(router.allowedMethods());
// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...');
when I Brower http://localhost:3000/,output 'Invalid URL!!!'. tell me why can't match '/' router ? thank you!
you can use '*' to match urls like 404 or invalid links:
router.all('*', function(ctx, next) {
//
});
upgrade your node version to 7.6.0. koa2 work on >=7.6.0
and code like
Koa=require('koa')
var app = new Koa();
router = require('koa-router')()
// parse request body:
//add url-route:
router.get('/hello/:name', (ctx, next) => {
var name = ctx.params.name;
ctx.response.body =`<h1>Hello, ${name}!</h1>`;
});
router.get('/', function (ctx, next) {
console.log(ctx.request);
ctx.body = '<h1>Index</h1>';
});
router.all('/login', function (ctx) {
ctx.redirect('/');
ctx.status = 301;
});
app.use(function (ctx,next){
this.body = `Invalid URL!!!${ctx.request.method} ${ctx.request.url}`;
ctx.response.type = 'text/html';
ctx.response.body = this.body;
next()
});
app.use(router.routes())
.use(router.allowedMethods());
// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...');

Resources