I am writing the mocha unit test for the my express router.
I found that however I try to stub the middleware, it still execute the middleware code.
Here is my router & test, could anyone figure out?
Router:
import { aMiddleware, bMiddleware, cMiddleware } from '../middleware.js';
router.post('/url', aMiddleware, bMiddleware, cMiddleware, function(req, res) { ... }
Middleware:
AuthMiddleware.aMiddleware = async (req, res, next) => {
console.log('in real middleware');
next();
}
Test:
var authMiddleware = require('../../middleware/auth.js');
describe('Test', async () => {
before(function (done) {
_STUB_MIDDLEWARE_A = sinon.stub(authMiddleware, 'aMiddleware');
_STUB_MIDDLEWARE_A.callsArg(2);
}
after(function (done) {
_STUB_MIDDLEWARE_A.restore();
}
}
terminal will show the console.log('in real middleware') in middleware
This is likely because the stub happened after the module has been loaded already. You probably need to clear the cache first for your router file and then load it in again after the stubbing because es6 will cache the imported modules.
Related
My client given me requirement to encrypt decrypt all request response. So for all encrypted request we wrote down the express middleware to get decrypted request. which is the simple part but while sending response we also have to encrypt response.
One way to do write common function to encrypt data and call that function from all routes. which is time consuming part because we have more than 50+ routes in project. So i was thinking to write middleware like we have done for request which capture response before we send and then we encrypt response after that we send encrypt response to client.
I have searched for solution in google not got any proper solution which worked for me.
routes.js
router.post('/getUserData', verifyApiKey, async function (req, res, next) {
let user = await getUserData();
res.status(200).send(user)
});
middlware.js
class EncryptDecryptRequestResponse {
async encryptResponse(req, res, next) {
console.log('Called encryptResponse');
console.log('res.body', res.body);
res.body = encryptData(res.body)
next();
}
}
App.js
// Middleware to decrypt request
app.use(decryptRequest);
app.use('/', indexRouter);
// Middleware to encrypt response
app.use(encryptResponse);
but the problem is that i am not getting any console.log from middleware. this is the solution which i used
I tried to reproduce the problem you're having with overwriting res.send(), but it works fine for me. You need to make sure to setup the interceptor middleware before you define your routes. Consider this simple example:
const express = require('express');
const app = express();
function encryptResponseInterceptor(req, res, next) {
const originalSend = res.send;
res.send = function () {
arguments[0] = encryptResponse(arguments[0]);
originalSend.apply(res, arguments);
};
next();
}
function encryptResponse(originalData) {
// place your encryption logic here, I'm just adding a string in this example
return originalData + " modified";
}
// fake method that returns resolves after 1s just for testing
function getUserData() {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000)
})
}
app.use(encryptResponseInterceptor);
app.get("/test", async (req, res, next) => {
await getUserData();
res.status(200).send("will be changed");
})
app.listen(3000, () => {
console.log("server started on 3000");
});
I wrote a Node/Typescript app that used data from Firebase Cloud Firestore. The app worked perfectly and I was able to test my endpoints fine using simple mocha commands of the generated .js file. Here is an example of one of the test scripts:
import * as supertest from 'supertest'
import app from '../App'
describe('Risk API', () => {
it('works to get /', () =>
supertest(app)
.get('/risks')
.expect('Content-Type', /json/)
.expect(200)
)
it('does not allow put at top level', () =>
supertest(app)
.put('/risks')
.expect(403)
)
})
Here is the App.js that is referring to (imports and declarations excluded):
let riskRouter = require('./routes/Risk')
class App {
public express
constructor () {
this.express = express()
this.mountRoutes()
}
private mountRoutes (): void {
const router = express.Router()
router.get('/', (req, res) => {
res.json({
message: 'Hello World!'
})
})
this.express.use(bodyParser.json());
this.express.use(cors({ origin: true }))
this.express.use('/', router)
this.express.use('/risks', functions.https.onRequest(riskRouter))
}
}
export default new App().express
Here is the Risk router with only the GET endpoint:
const express = require('express');
const bodyParser = require('body-parser');
const riskRouter = express.Router();
import { firestore, firebasestore } from '../firebase/firebase';
riskRouter.use(bodyParser.json());
riskRouter.route('/')
.get((req,res,next) => {
return firestore.collection('risks').get()
.then(snapshot => {
let risks = [];
snapshot.forEach(doc => {
const data = doc.data()
const _id = doc.id
risks.push({_id, ...data });
});
res.send(risks)
})
.catch( err => res.json({error: err}))
})
// POST, PUT and DELETE are implemented here but not needed for this discussion
module.exports = riskRouter
When I tried to migrate this to Firebase, I basically copied the entire node application to the /functions directory and made the following change to the App.ts file
let riskRouter = require('./routes/Risk')
class App {
public express
constructor () {
this.express = express()
this.mountRoutes()
}
private mountRoutes (): void {
const router = express.Router()
router.get('/', (req, res) => {
res.json({
message: 'Hello World!'
})
})
this.express.use(bodyParser.json());
this.express.use(cors({ origin: true }))
this.express.use('/', router)
this.express.use('/risks', functions.https.onRequest(riskRouter))
}
}
export default new App().express
In both cases, the test command in package.json is
"test": "tsc && mocha lib/**/*.spec.js"
Also, the Risk router is identical in both cases.
In the case that works, all the test simply run cleanly. Also, they are making calls to the external Firebase backend
In the case that fails, I get the following output:
Risk API
1) works to get /
0 passing (2s)
1 failing
1) Risk API works to get /:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
at listOnTimeout (internal/timers.js:549:17)
at processTimers (internal/timers.js:492:7)
All of my endpoints use some form of res.send(), res.json(), etc which I read is sufficient and the explicit use of done() is not needed. If I'm wrong about that, I'd like to know as well as the proper syntax to fix it.
I also tried running mocha directly on the generated test script using the --timeout 15000 option, but got the same result.
Any suggestions would be greatly appreciated!
Can you try the following:
import * as supertest from 'supertest'
import app from '../App'
describe('Risk API', (done) => {
it('works to get /', () =>
supertest(app)
.get('/risks')
.expect('Content-Type', /json/)
.expect(200, done)
)
it('does not allow put at top level', (done) =>
supertest(app)
.put('/risks')
.expect(403, done)
)
})
Forgive me for these heretical speeches, but I consider express to be the best library for api building from the developer experience point of view. But what stops me from using it everywhere is that everyone keeps saying (and confirming with benchmarks) that it is slow.
I try to choose an alternative for myself, but I canэt find what suits me.
For example with express I can simply organize the following structure:
userAuthMiddleware.js
export const userAuthMiddleware = (req, res, next) => {
console.log('user auth');
next();
};
adminAuthMiddleware.js
export const adminAuthMiddleware = (req, res, next) => {
console.log('admin auth');
next();
};
setUserRoutes.js
export const setUserRoutes = (router) => {
router.get('/news', (req, res) => res.send(['news1', 'news2']));
router.get('/news/:id', (req, res) => res.send(`news${req.params.id}`));
};
setAdminRoutes.js
export const setAdminRoutes = (router) => {
router.post('/news', (req, res) => res.send('created'));
router.put('/news/:id', (req, res) => res.send('uodated'));
};
userApi.js
imports...
const userApi = express.Router();
userApi.use(userAuthMiddleware);
// add handlers for '/movies', '/currency-rates', '/whatever'
setUserRoutes(userApi);
export default userApi;
server.js
imports...
const app = express();
app.use(bodyparser); // an example of middleware which will handle all requests at all. too lazy to come up with a custom
app.use('/user', userApi);
app.use('/admin', adminApi);
app.listen(3333, () => {
console.info(`Express server listening...`);
});
Now it is very easy for me to add handlers to different "zones", and these handlers will pass through the necessary middlewares. (For example users and admin authorization goes on fundamentally different logic). But this middlewares I add in one place and don't think about it anymore, it just works.
And here I am trying to organize a similar flexible routing structure on fastify. So far I haven't succeeded. Either the documentation is stingy, or I'm not attentive enough.
Fastify middlewares that added via 'use' gets req and res objects from the http library and not from the fastify library. Accordingly, it is not very convenient to use them - to pull something out of the body it will be a whole story.
Please give an example of routing in fastify a little more detailed than in the official documentation. For example similar to my example with user and admin on express.
I organize my routes like this:
fastify.register(
function(api, opts, done) {
api.addHook('preHandler', async (req, res) => {
//do something on api routes
if (res.sent) return //stop on error (like user authentication)
})
api.get('/hi', async () => {
return { hello: 'world' }
})
// only for authenticated users with role.
api.register(async role => {
role.addHook('preHandler', async (req, res) => {
// check role for all role routes
if (res.sent) return //stop on error
})
role.get('/my_profile', async () => {
return { hello: 'world' }
})
})
done()
},
{
prefix: '/api'
}
)
Now all request to api/* will be handled by fastify.
I'm trying to add Sequelize transactions to my Express app, but I'm unsuccessful. I'm using async/await across the app, and I've created the namespace using the 'cls-hooked' package as instructed on the docs for Sequelize transactions.
Sequelize.useCLS(require('cls-hooked').createNamespace('db'));
My middleware is pretty simple and looks something like this
module.exports = () => (req, res, next) => sequelize.transaction(async () => next());
and in app.js
app.use(sequelizeTransaction());
app.use('/api', apiRoutes);
I've also tried using the middleware directly on routes, but I get the same result as above
router.post('/', sequelizeTransaction(), async (req, res) => {
await serviceThatDoesTwoDBOperations();
});
The result is that I get the transaction, but only around the first DB operation. Everything after that is ignored, and rollbacks aren't happening on errors. I'm probably doing something obviously wrong, but I can't put my finger on it.
I was having the same issue as you. This is how I made it work. For some reason sequelize was clearing the transaction on the namespace before it was actually completed.
Also make sure that you are using node > 8.5 for the async_hooks.
export const transactionMiddleware = async (req, res, next) => {
namespace.bindEmitter(req);
namespace.bindEmitter(res);
namespace.bind(next);
namespace.run(async () => {
const transaction = await sequelize.transaction();
namespace.set('transaction', transaction);
onFinished(res, (err) => {
if (!err) {
transaction.commit();
} else {
transaction.rollback();
}
});
next();
});
};
I hope that works for you :)
Hi i want to do unit testing on my express js code i want to mock data so after searching multiple websites and blogs i found this library but i am not clear how can i use this library for mocking or data.
My testing code is
var request = require('supertest');
var server = require('./app');
var chai = require('chai');
var chaiHttp = require('chai-http');
var server = require('./app');
var should = chai.should();
chai.use(chaiHttp);
describe('loading express', function () {
it('responds to /', function testSlash(done) {
request(server)
.get('/')
.expect(200, done);
});
it('404 everything else', function testPath(done) {
request(server)
.get('/foo/bar')
.expect(404, done);
});
it('responds to /customers/getCustomerData', function testPath(done) {
request(server)
.get('/customers/getCustomerData?id=0987654321')
.end(function(err, res){
res.should.have.status(200);
res.body.should.be.a('object');
res.body.status.should.equal("success");
res.body.data.customerId.should.equal("0987654321");
done();
});
});
});
Currently this code is fetching data from database but i want Unit testing using mock data. How can i achieve this?
__EDIT__
I want to test the code which is written inside Express js routes file. this routes i am calling inside app.js file like this
var customers = require('./routes/customers');
app.use('/customers', customers);
now the code which customers route file contain is
function getCustomerData(req, res, next) {
var response = {};
var cb = function (response) {
res.send(response);
}
var modelObj = req.models.customer_master;
var data = req.query;
controllers.customers.get(modelObj, data, cb);
};
router.get('/getCustomerData', function (req, res, next) {
getCustomerData(req, res, next);
});
I want to test the response of "get" method using mock data
You want to stub your controller middlewares i guess. As you didn't provide any server side code, i just asume some things:
app.get('/', rootController.get);
Now you want to stub this controller:
it('responds to /', function testSlash(done) {
const rootController = require('./path/to/your/controller');
const rootControllerStub = sinon.stub(rootController, "get",
function(req, res, next){
res.status(200).json({stubbed: 'data'});
});
request(server)
.get('/')
.expect(200)
.expect({stubbed: 'data'})
.end(done);
});
If you wish to mock, you can use sinon express mocks here or if you want to test the actual response data, JSON, use this example
The express route, in the example, takes a parameter and returns a JSON
it('should respond with JSON data', function (done) {
request(server)
.get('/about/jv')
.expect(200)
.end(function (err, response) {
assert.equal(response.header['content-type'], 'application/json; charset=utf-8');
assert.deepEqual(response.body, {
"data":{
"username":"hellojv"}
});
done();
});
but as mentioned above, if you want to use sinon then use the mock library. The example uses Mocha and supertest.
Additionaly, if you write many test files, the stub may not work because of cache. I have to clear cache before initializing the stub and the server. The order is also important.
// have to clear every module which belongs to the require chain
// APP require FOO ROUTE require FOO CONTROLLER require BAR LIB
const caches = [
'../app',
'../routes/foo',
'../controller/foo',
];
caches.forEach(cache => {
delete require.cache[require.resolve(cache)];
});
// mock
const bar = require('../lib/bar');
sinon.stub(bar, 'bar').callsFake(async function() {
return null;
});
app = require('../app');
// ... then the test ...
I found this thread helpful.