6

Whenever a Mongoose model is attempted to load after it's already loaded, an error is thrown, such as:

error: uncaughtException: Cannot overwrite Account model once compiled. date=Fri Feb 26 2016 10:13:40 GMT-0700 (MST), pid=19231, uid=502, gid=20, cwd=/Users/me/PhpstormProjects/project, execPath=/usr/local/Cellar/node/0.12.4/bin/node, version=v5.2.0, argv=[/usr/local/Cellar/node/0.12.4/bin/node, /usr/local/Cellar/node/0.12.4/bin/lab], rss=73306112, heapTotal=62168096, heapUsed=29534752, loadavg=[1.6005859375, 1.84716796875, 1.8701171875], uptime=648559 OverwriteModelError: Cannot overwrite Account model once compiled.

Which I'm fine with, but now that I'm writing unit tests for my models, I'm running into an issue.

Just some basic info about the file structure...

I have all the Mongoose models in separate files, located inside the src/models/ folder, and to load these models, one simply has to require the folder, passing a Mongoose object to it, and the src/models/index.js file will load all the models, and return an object of the models. The index.js file can be seen here (And not that its relevant, but the model names are basically the filename, without the .js)

Now the Unit tests for the models are also split up into separate files. Theres one test file for each model. And even though each unit test file focuses on a specific model, some of them use other models as well (for before/after tasks).

Initial Problem

I just created the 2nd unit test file, and when I execute each one independently, they work just fine. But when I execute all of them, I receive the above error, stating that I'm attempting to load the models more than once. Which since I require the ./models in each unit test case, I am loading them more than once.

First Resolution Attempt

I thought that maybe I could clear all of the loaded models via after() in each separate unit test file, like so:

after(function(done) {
    mongoose.connection.close(function() {
        mongoose.connection.models = {}
        done()
    })
})

Which didn't work at all (No new errors, but the same Cannot overwrite Account model once compiled error(s) persisted)

Second Resolution Attempt (semi-successful)

Instead of the models throwing an error on the last line, when it attempts to return the Mongoose.model(), I insert some logic in the top of the model, to check if the model is loaded, and if so, return that model object:

const thisFile  = path.basename( __filename ).match( /(.*)\.js$/ )[ 1 ]
const modelName = _.chain( thisFile ).toLower().upperFirst().value()

module.exports = Mongoose => {
    // Return this model, if it already exists
    if( ! _.isUndefined( Mongoose.models[ modelName ] ) )
        return Mongoose.models[ modelName ]

    const Schema = Mongoose.Schema

    const appSchema = new Schema( /* ..schema.. */)

    return Mongoose.model( modelName, appSchema )
}

I'm trying that out in my models right now, and it seems to work alright, (alright meaning I don't get the errors listed above, saying I'm loading models multiple times)

New Problem

Now whenever the unit tests execute, I receive an error, the error displays once per a model, but its the same error:

$ lab

  ..................................................
  ...

Test script errors:

Cannot set property '0' of undefined
      at emitOne (events.js:83:20)
      at EventEmitter.emit (events.js:170:7)
      at EventEmitter.g (events.js:261:16)
      at emitNone (events.js:68:13)
      at EventEmitter.emit (events.js:167:7)

Cannot set property '0' of undefined
      at emitOne (events.js:83:20)
      at EventEmitter.emit (events.js:170:7)
      at EventEmitter.g (events.js:261:16)
      at emitNone (events.js:68:13)
      at EventEmitter.emit (events.js:167:7)

Cannot set property '0' of undefined
      at emitOne (events.js:83:20)
      at EventEmitter.emit (events.js:170:7)
      at EventEmitter.g (events.js:261:16)
      at emitNone (events.js:68:13)
      at EventEmitter.emit (events.js:167:7)

There were 3 test script error(s).

53 tests complete
Test duration: 1028 ms
No global variable leaks detected

There isn't too much details to go off of in that stack trace..

I'm not sure if its caused by the code I added into each model, checking if it's already loaded, if it was, it would either show up when I execute a single unit test, or it would only show that Cannot set property '0' of undefined twice (Once for a successful initial model load, then twice for the next two... I would think)

If anyone has any input, I would very much appreciate it! Thanks

Updates

I tried running lab --debug to get more info, and while it doesn't show any stack traces around the errors showing up, it doubles them... which is odd. So if there was 2 when executing just lab, lab --debug shows 4

Also, I use Winston to do my logging. If I change the log level to debug, which shows a lot of debug entries in the console, it doesn't show any entries around these errors... So that makes me think it may not be caused by my scripts, but rather something in the unit testing dependencies?

The errors say they originate from the error.js file, but don't say much else. I tried to find an error.js via find . -name 'events.js', with no results.. Odd

Justin
  • 1,959
  • 5
  • 22
  • 40

1 Answers1

0

I think the code you placed into each model is a hack. During the normal execution, require has "global" effect - once you import the module, it will not be imported second time.

Probably this normal flow is changed during the tests, but that means that it is better to find a solution which can be locally implemented inside the tests.

It also looks like you have the problem similar to what is discussed in this issue - OverwriteModelError with mocha 'watch'.

There are some solutions to try:

1) Create new mongoose connection each time:

var db = mongoose.createConnection()

2) Run the mocha via nodemon. This one looks puzzling for me, but still worth trying, maybe it makes each test to run completely independently. I also assume you use mocha for tests:

nodemon --exec "mocha -R min" test

3) Clear mongoose models and schemes after each test:

after(function(done){
  mongoose.models = {};
  mongoose.modelSchemas = {};
  mongoose.connection.close();
  done();
});
Borys Serebrov
  • 15,636
  • 2
  • 38
  • 54
  • I actually thought the same thing, but that would only make sense if the errors showed one less than the test files. Meaning if I have 5 unit test files using Mongoose/Mockgoose, then if you were right, I would think id only see the error 4 times, but I see it 5 times, unless I execute just one, or just one exists. In any case, I tried your solution, and it didnt seem to work. – Justin Mar 05 '16 at 15:44
  • 1
    @Justin Can you make an [mvce](http://stackoverflow.com/help/mcve) for your problem? I think I could find the solution having the full code example. – Borys Serebrov Mar 05 '16 at 16:35
  • Do you have a github user? Ill add you to the private repo, it uses Mockgoose, so you dont have to create a Mongo DB – Justin Mar 07 '16 at 15:06