1

I have a folder structure of:

- app
  - config
    - config.js // environment variables
    - express.js // creates an express server
  - passwords
    - passwords.controller.js
    - passwords.route.js
    - passwords.test.js
  - index.js // main app file

My index file loads the app asynchronously:

function initMongoose() {

  ...

  return mongoose.connect(config.mongo.host, {useNewUrlParser: true, keepAlive: 1})
    .then((connection) => {

      // Password is a function which connects the Password schema to the given connection
      Password(connection);
    })
    .catch((e) => {
      throw new Error(`Unable to connect to database: ${config.mongo.host}`);
    });
}

async init() {

  await initMongoose();

  const app = require('./config/express');
  const routes = require('./index.route');

  app.use('/api', routes);

  ...

  app.listen(3000, () => {
    console.log('server started');
  });
}

module.exports = init();

My test files are constructed like this:

// Load the app async first then continue with the tests
require('../index').then((app) => {

  after((done) => {
    mongoose.models = {};
    mongoose.modelSchemas = {};
    mongoose.connection.close();
    done();
  });

  describe('## Passwords API', () => {
    ...
  });
});

I'm starting the tests like this:

"test": "cross-env NODE_ENV=test ./node_modules/.bin/mocha --ui bdd --reporter spec --colors server --recursive --full-trace"

Here's where the weirdness gets the better of me. Basically it loads passwords.controller.js before anything else, this is because of the --recursive option. This should not happen since index.js needs to load first so it can connect to mongoose etc before any of the tests runs, if it doesn't then this snippet from passwords.controller.js will throw MissingSchemaError: Schema hasn't been registered for model "Password". since the model Password haven't been setup at this point:

const Password = mongoose.connection.model('Password');

So I tried adding --require ./index.js before the --recursive option, this indeed loads other files before passwords.controller.js but the latter still runs before index.js have even finished.

The solutions here doesn't work because index.js doesn't run first.

How can I modify my test script to allow my index.js to finish before I run any test files?

Chrillewoodz
  • 27,055
  • 21
  • 92
  • 175
  • Do you have minimal repo one can test? Will save time to provide a solution – Tarun Lalwani Jun 23 '19 at 13:55
  • Why are you requiring index from your test? - you want to split index into 2 files: app.js & server.js - you should not listen in your mocha test, just in production code. - if you want to connect to mongoose in your test - use a before() – Brent Greeff Jun 23 '19 at 13:58
  • @BrentGreeff My setup is based on this https://github.com/kunalkapadia/express-mongoose-es6-rest-api, which requires the `index.js` file in each test file which in turn requires the `express.js` file. Since I'm working with multiple mongoose instances I had to tweak the code so it loads the app asynchronously, that's when it must load the `index.js` file first, and not when the test file requires it. I'm not sure what you mean when you say the tests should not listen, of course they have to listen or the tests won't be able to use the database. – Chrillewoodz Jun 24 '19 at 06:41
  • @TarunLalwani It's based on this https://github.com/kunalkapadia/express-mongoose-es6-rest-api, I just rewrote the `index.js` file basically to load the app asynchronously instead. Perhaps this helps you? – Chrillewoodz Jun 24 '19 at 06:42
  • @Chrillewoodz - your web server is listening. It should not in a mocha test. I am not sure why u need to structure your files like that. I also dont understand why you cant connect to the db explicitly from your test. – Brent Greeff Jun 25 '19 at 21:17

2 Answers2

1
describe('## Passwords API', function() {
  let appInstance;

   before((done) => {
      require('index.js').then((app) => {
          appInstance = app
          done();
      });
   });
});

Ref: https://mochajs.org/#asynchronous-hooks

Another possible pattern is to have a special helper file. That act like a singleton or some sort of cache. This allows the server to be bootstrap only once and it is shared for all the test. Note that this can cause unwanted side-effects in your unit tests but can speed up tests that do not affect the app object.

For instance testUtil.js

let app = null;
async function getApp(){
   if(app){
      return app;
   }
   app = await require('index.js')
   return app
}

module.export = {
   getApp,
}

and in your test file

const helper = require('[your path]/testUtil.js')
describe('## Passwords API', function() {
  let appInstance;

   before((done) => {
      helper.getApp().then((app) => {
          appInstance = app
          done();
      });
   });
});

I would also suggest putting all the tests in a separate folder (called maybe "tests"). This would ensure that only the tests are loaded by --recursive. Another perk is that in production you can skip this folder

Plpicard
  • 1,066
  • 6
  • 15
0

You could technically run your index.js file and instead of using mocha via npm test (CLI) you could actually run your test with mocha programmatically after index.js. Mocha Programmatically example

import Mocha from 'mocha'; 
import fs from 'fs';
import path from 'path';

// Instantiate a Mocha instance.
const mocha = new Mocha();

const testDir = 'some/dir/test'

// Add each .js file to the mocha instance
fs.readdirSync(testDir).filter(function(file) {
    // Only keep the .js files
    return file.substr(-3) === '.js';

}).forEach(function(file) {
    mocha.addFile(
        path.join(testDir, file)
    );
});

// Run the tests.
mocha.run(function(failures) {
  process.exitCode = failures ? 1 : 0;  // exit with non-zero status if there were failures
});
Michael
  • 13
  • 3
  • What if the tests are in separate folders? Is there a programatic way of running `--recursive`? – Chrillewoodz Jun 25 '19 at 08:04
  • you can run test in separate folders by using mocha.addFile but to run the recursive flag that is only a cli function – Michael Jun 25 '19 at 15:29