10

I'm trying to use mocha to test my express app. My folder structure is:

myapp
|-app
|--models
|-test
|--mocha-blanket.js
|--mocha
|--karma
|-server.js

server.js is my express server. I had that before in options.require, but the documentation said to use a blanket.js. My mocha-blanket.js is:

var path = require('path');
var srcDir = path.join(__dirname, '..', 'app');

require('blanket')({
  // Only files that match the pattern will be instrumented
  pattern: srcDir
});

My Gruntfile has:

mochaTest:
  options:
    reporter: "spec"
    require: 'test/mocha-blanket.js'
    # require: "server.js"
  coverage:
    options:
      reporter: 'html-cov',
      captureFile: 'mocha-coverage.html'
    src: ["test/mocha/**/*.js"]

The error I'm getting is:

>> Mocha exploded!
>> MissingSchemaError: Schema hasn't been registered for model "Company".
>> Use mongoose.model(name, schema)
>>     at Mongoose.model (/myapp/node_modules/mongoose/lib/index.js:315:13)
>>     at Object.<anonymous> (/myapp/test/mocha/controllers/company.js:4:22)
>>     at Module._compile (module.js:456:26)
>>     at Module._extensions..js (module.js:474:10)

I'm sure I'm doing something (or a lot of things) incorrectly. But I'm not sure what. Any ideas?

Zachary Jacobi
  • 1,073
  • 10
  • 26
Shamoon
  • 41,293
  • 91
  • 306
  • 570
  • The error seems not to be related to Mocha or Blanket. It's a very naive question, but does it fails only in Mocha ? Does it works in real when you're launching your server ? If not, adding some details about your models would help to diagnose. – Feugy Feb 20 '14 at 12:39
  • It works fine when I launch my server - it's only when grunt testing with mocha – Shamoon Feb 20 '14 at 14:20

3 Answers3

9

I think your testing code isn't properly initializing your application, specifically initialization of the Mongoose schema and models.

In mocha/controllers/company.js, you're probably using code similar to this:

var Company = mongoose.model('Company');

However, that will raise the error that you're getting if the initialization of the Company model (where you hook up the model with the schema) was skipped.

To give a very short standalone example, this code will fail with the same error:

var mongoose = require('mongoose');
var Company  = mongoose.model('Company');

This code, with the added initialization, works fine:

var mongoose = require('mongoose');

mongoose.model('Company', new mongoose.Schema());

var Company = mongoose.model('Company');
robertklep
  • 198,204
  • 35
  • 394
  • 381
  • My server code properly loads though. And you are right, I have: `var Company = mongoose.model('Company');` – Shamoon Feb 20 '14 at 17:49
  • @Shamoon add a `console.log` at the code where you initialize your `Company` model and see if it gets triggered. If not, it's not being called properly :) – robertklep Feb 20 '14 at 17:54
  • `mongoose.model('Company', new mongoose.Schema());`, won't that not use the Schema that I've defined in my model file? – Shamoon Feb 20 '14 at 21:46
  • @Shamoon that will initialize the `Company` model with an empty schema, which sounds a bit strange. – robertklep Feb 21 '14 at 06:55
  • I think I see what you're saying, that Blanket doesn't know about my schemas because they are loaded in my `express server`, which blanket doesn't run. So can I have some sort of a prerunner to run a script that will load the models? – Shamoon Feb 21 '14 at 14:07
  • 1
    @Shamoon I would just move the initialization of your schema's to a separate file that's `require`'ed when needed. – robertklep Feb 21 '14 at 14:24
  • Only in the tests, or for my whole project? – Shamoon Feb 21 '14 at 14:42
  • 1
    I would do that for your whole project. – robertklep Feb 21 '14 at 14:42
2

You get this error because you have not explicitly 'loaded' your models to mongoose. I have solved this issue with: 1 Models init script:

module.exports = {
  storage:{
    sample: require('models/storage/sample'),
    other models scoped to "storage" connection...
  },
  accounts: {
    company: require('models/accounts/company'),
    etc...
  }
};

this is kicked off once on each mongoose.createConnection callback:

var accounts = mongoose.createConnection(config.get('mongoose:accounts:uri'), config.get('mongoose:accounts:options'), function(err){
  if (err) throw err;
  console.log('connection to accounts database open');
  require('models/init').accounts;
});

it runs all your models declaration code and later in tests you have all your models registered.

Ensure database connection middleware. This guy makes you sure that connections are open and init scripts run successfully.

var storage = require('models').storage;
var accounts = require('models').accounts;

module.exports = function ensureDbConnectionMiddleware(req, res, next){
  var count = 0;
  var interval = setInterval(function(){
    if (storage._readyState === 1 && accounts._readyState === 1){
      clearInterval(interval);
      return process.nextTick(function(){
        next();
      });
    }else{
      if (count++ > 50){
        res.json({status: 0, message: 'Database offline'});
        throw new Error('Database is offline');
      }
    }
  }, 100);
};

Later in tests

var should = require('should');
var express = require('express');
var app = express();
app.use(express.bodyParser());
app.use(require('middleware/ensureDbConnection'));
require('routes')(app);
//etc...

I'm not sure if this is the best solution, but it works quite well. It certainly has one major caveat: you have to keep all this stuff in sync. However, you can try to delegate this to grunt.

Eugene Kostrikov
  • 6,799
  • 6
  • 23
  • 25
1

For Mocha, you need to wait for the MongoDB connection to be open before you try to use a model.

  var mongoose = require('mongoose');

  var Company;

  describe('Unit Test', function () {
    before(function(done) {
      mongoose.connection.once('open', function() {
        Company = mongoose.model('Company');
        done();
      });
    });

    ... tests here ...

  });
Splaktar
  • 5,506
  • 5
  • 43
  • 74