Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mongoose model overwrite error #27

Closed
csugden opened this issue Jun 4, 2014 · 12 comments
Closed

Mongoose model overwrite error #27

csugden opened this issue Jun 4, 2014 · 12 comments

Comments

@csugden
Copy link

csugden commented Jun 4, 2014

I've used rewire with a Mongoose model in a jasmine spec, and also used require to load the same model in a different spec. I get this error:

{ message: 'Cannot overwrite `Organisation` model once compiled.', name: 'OverwriteModelError' }

It looks like it's asking Mongoose to define the same model twice, whereas if we used require both times this does not happen.

Is there a workaround?

Thanks.

@jhnns
Copy link
Owner

jhnns commented Jun 4, 2014

Mhmm interesting. I think this could be solved if you stored the rewired module under require.cache so node doesn't execute the code twice:

var myModel = rewire("./myModel");
require.cache[require.resolve("./myModel")] = myModel;

// than somewhere else
var myModel = require("./myModel"); // returns the rewired module

It's important that rewire is the first one that executes the module since rewire always creates a fresh instance. node's require always looks up the require.cache before evaling the code.

@jayfresh
Copy link

jayfresh commented Jun 5, 2014

Hi, I've been trying this (I work with @csugden) and I'm getting undefined for the result of myModel. Specifically:

var rewire = require('../node_modules/rewire'),
  myModel = rewire('./lib/myModel');
require.cache[require.resolve('./lib/myModel')] = myModel;

// in another file
var myModel = require('./lib/myModel');
console.log(myModel); // undefined

@jayfresh
Copy link

jayfresh commented Jun 5, 2014

As a follow-up, I noted that the object structure of what is put in the require cache by require is a bit different to what is put there by overwriting the cache. For example, modules in the cache that require has put there have a "paths" property.

@jhnns
Copy link
Owner

jhnns commented Jun 5, 2014

Oh yes, you're right. require.cache stores the module, not module.exports. A quickfix would be:

var myModel = rewire('./lib/myModel');
require.cache[require.resolve('./lib/myModel')] = {
    exports: myModel
};

... thus faking the actual module-object. As you can see in node's source code, they're directly returning module.exports if module is defined.

But that's surely not a clean solution. Maybe rewire should provide a method to directly store the rewired module in the require.cache, like:

var myModel = rewire.global("./lib/myModel");

require("./lib/myModel") === myModel; // true
rewire("./lib/myModel") === myModel; // false

But what happens when the require.cache already contains an instance of the module? Is rewire allowed to replace the module? This could have serious implications...

Maybe there's also another solution? Any suggestions?

@jayfresh
Copy link

jayfresh commented Jun 5, 2014

The quickfix you suggested worked. Jasmine-node seems to collect the spec files together alphabetically, so I've got "aFixSpec.js" that gets loaded first.

I also wanted to be able to run the tests independently, which meant keeping the use of rewire in the spec files. I got around this by using the same technique to overwrite require's cache of rewire and replace it with require (also in aFixSpec.js):

require.cache[require.resolve('rewire')] = {
  exports: require
};

This means when you run all the tests at once, any further use of rewire outside of aFixSpec.js uses require and hence goes through the same cache, which looks like a noop; but as I mentioned above, this allows the rewire method to remain in the test specs so they can be run independently.

@jhnns
Copy link
Owner

jhnns commented Jun 5, 2014

Mhmm I don't get this fix ^^. If a test uses rewire() but receives require() instead, wouldn't that break the test? The returned module won't have a __set__- or __get__-method.

Another solution which came in my mind is to separate the registering-part from the definition. So instead of writing:

var Cat = mongoose.model('Cat', { name: String });

you would write

// CatSchema.js
module.exports = mongoose.Schema({
    name: String
});
// Cat.js
module.exports = mongoose.model('Cat', CatSchema);

I don't know what exactly you're trying to test and mock with rewire, but separating these two things is a good idea anyway.

@jhnns
Copy link
Owner

jhnns commented Oct 28, 2014

Close?

@jayfresh
Copy link

ok!

@jhnns jhnns closed this as completed Oct 28, 2014
@Trindaz
Copy link

Trindaz commented Dec 19, 2014

For future googlers, you could also try using sinon to enable rewire usage with mongoose models. Here is an example that worked for me:

  function disableNewModelDefinitions() {
    var originalModel = mongoose.model;
    sinon.stub(mongoose, 'model', allowModelRetrieval);
    function allowModelRetrieval() {
      if (arguments.length === 1) {
        return originalModel.apply(mongoose, arguments);
      }
    }
  }

@jhnns
Copy link
Owner

jhnns commented Dec 21, 2014

Nice 👍

@neverfox
Copy link

@Trindaz Could you provide an example of how you actually use that function? Thanks!

@neverfox
Copy link

I believe this would work in most scenarios where you need to re-register a mongoose model:

delete mongoose.connection.models['Cat'];
// proceed with rewire, proxyquire, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants