TL;DR - What is the way to test the resources in a Node API (Express) that uses JWT for Authentication with the token itself only granted to a username/password login?
I’m kinda new to testing and wanted to get some advice. The end goal is to have a fully tested API and then start learning how to get that hooked up to a Continuous Integration solution.
Technologies in use
API Information
The API has various resources - the specifics of which aren’t important to this query but lets just pretend it’s the ubiquitous Todo app for simplicity’s sake.
Each individual resource saved in the database is associated with a single User.
The API uses JWT for authentication across the various resource endpoints. The token itself contains the unique User ID which is stored against the resource in a Mongo database. To get the token itself requires a user to first signup (which returns a token) and then login to get a new token.
Pretend code.
I’m going to simplify the code below and not make use of any environment configs, etc…
app.js
var express = require('express');
var app = express();
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var passport = require('passport');
mongoose.connect('mongodb://localhost/somedatabasename');
app.set('port', process.env.PORT || 3000);
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(passport.initialize());
// ... Passport JWT Strategy goes here - omitted for simplicity ...
var userRouter = require('./api/users/routes');
app.use('/users', userRouter);
var todoRouter = require('./api/todos/routes');
app.use('/todos', todoRouter);
app.listen(app.get('port'), function() {
console.log('App now running on http://localhost:' + app.get('port'));
});
./api/todos/routes.js
var router = require('express').Router();
var controller = require('./controller');
var passport = require('passport');
router.route('/')
.all(passport.authenticate('jwt', { session: false}))
.get(controller.getAll)
.post(controller.create);
router.route('/:id')
.all(passport.authenticate('jwt', { session: false}))
.get(controller.getOne)
.put(controller.update)
.delete(controller.delete);
module.exports = router;
./api/users/routes.js
var router = require('express').Router();
var controller = require('./controller');
var passport = require('passport');
router.route('/')
// User signup
.post(controller.create);
router.route('/me')
// User Login
.post(passport.authenticate('local', { session: false}), controller.login)
// Get current user's data
.get(passport.authenticate('jwt', { session: false}), controller.getOne)
// Update current user's data
.put(passport.authenticate('jwt', { session: false}), controller.update)
// Delete current user
.delete(passport.authenticate('jwt', { session: false}), controller.delete);
module.exports = router;
./api/users/model.js
var mongoose = require('mongoose');
var bcrypt = require('bcrypt');
var UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
}
});
// ... for simplicity imagine methods here to
// - hash passwords on a pre save hook using bcrypt
// - compare passwords using bcrypt when logging in
module.exports = mongoose.model('User', UserSchema);
./api/todos/model.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var momentSchema = new Schema({
title: {
type: String
},
// Bunch of other fields here...
_user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
});
module.exports = mongoose.model('Moment', momentSchema);
I have omitted some of the example code to keep it clean and simple.
For example, the User's controller would include the Models and its functions would:
The Todo's controllers would do something similar - just interacting with Mongo data via Mongoose but the queries would always include the User ID to associate the specific (for example) Todo item with the authenticated User (authenticated via JWT).
Testing Conundrum
How would I go about testing something like this using a combination of Mocha, Chai and SuperTest?
Would I:
How would the testing work locally when developing versus when you do a deployment using some CI tool (something I have yet to even get to in my studies)?
Any assistance would be much appreciated and I hope I've given enough info with the dummy data/code above :/
To authenticate a user, a client application must send a JSON Web Token (JWT) in the authorization header of the HTTP request to your backend API. API Gateway validates the token on behalf of your API, so you don't have to add any code in your API to process the authentication.
During testing, you would normally mock your mongo DB (something like mongo-mock
. This way, you do not need an actual database running to run your tests (you are not testing the database, but your code).
During testing, you would replace the mongodb
with mongo-mock
and then run your test. To get your token, you would need to post to your /me
URL with valid mocked credential, that endpoint would return the token, which you would then use on your next call to test your other endpoint.
On the token side of things, I usually check it at the beginning of the request before entering the other endpoints. (I have not used passport but the idea is):
app.use(validate_jwt_middleware);
app.use('/users', userRouter);
This way, if the token is invalid, it's invalid for the whole site, not only your section.
Also, I'm not using SuperTest, but chai-http, so I can't help you with your specifics.
Hope this helps,
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With