How to test a Node API that uses JWT Authentication (with User login to get token)
<p>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?</p>

<p>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.</p>

<p><strong>Technologies in use</strong></p>

<ul>
<li>I have written an API in <strong>Node</strong> using <strong>Express</strong>.</li>
<li><strong>Mongo</strong> is the database.  </li>
<li><strong>Mongoose</strong> is used as the ODM.  </li>
<li><strong>jsonwebtoken</strong> package is used to create/verify tokens.</li>
<li><strong>Passport</strong> is used to easily add User Authentication as Express middleware on the routes.</li>
</ul>

<p><strong>API Information</strong></p>

<p>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. </p>

<p>Each individual resource saved in the database is associated with a single User.    </p>

<p>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.</p>

<p><strong>Pretend code.</strong></p>

<p>I’m going to simplify the code below and not make use of any environment configs, etc…</p>

<p>app.js</p>

<pre><code>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'));
});
</code></pre>

<p>./api/todos/routes.js</p>

<pre><code>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;
</code></pre>

<p>./api/users/routes.js</p>

<pre><code>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;
</code></pre>

<p>./api/users/model.js</p>

<pre><code>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);
</code></pre>

<p>./api/todos/model.js</p>

<pre><code>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);
</code></pre>

<p>I have omitted some of the example code to keep it clean and simple.  </p>

<p>For example, the <strong>User's</strong> controller would include the Models and its functions would:</p>

<ul>
<li><strong>controller.create</strong> - signup for new users (returns token) </li>
<li><strong>controller.login</strong> - after username/password combination confirmed by Passport Local then return a valid token</li>
<li><strong>controller.getOne</strong> - based on User ID retrieved from JWT token, return the user's data from Mongo using Mongoose.</li>
<li><strong>controller.update</strong> - Update the User's data in Mongo using Mongoose</li>
<li><strong>controller.delete</strong> - Delete the User's data in Mongo using Mongoose</li>
</ul>

<p>The <strong>Todo's</strong> 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).</p>

<p><strong>Testing Conundrum</strong></p>

<p>How would I go about testing something like this using a combination of Mocha, Chai and SuperTest?</p>

<p>Would I:</p>

<ul>
<li>create a test database within Mongo and have the connection string be different in the tests? This would mean saving actual data stored in the database for testing.</li>
<li>Mock the data somehow and not use a test database at all?  But then how is user saving / login handled to retrieve the token?</li>
</ul>

<p>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)?</p>

<p>Any assistance would be much appreciated and I hope I've given enough info with the dummy data/code above :/</p>