Mongoose: OverwriteModelError 与 mocha 'watch'

创建于 2012-12-14  ·  61评论  ·  资料来源: Automattic/mongoose

我刚刚从 3.4.0 更新到 3.5.1,并且在“观看”模型上的 Mocha 测试时,每次重新加载文件时,我都会从重新“要求”中收到 OverwriteModelError - 我猜是重新定义 - 模型。

使“覆盖”成为错误肯定有一些价值,但我现在要回到 3.4.0,因为这太痛苦了。

最有用的评论

还有另一种选择:从所有模型和模式中清除猫鼬。
在我的测试代码中,我添加了:

 mongoose.models = {};
 mongoose.modelSchemas = {};

它工作正常,至少对我来说。

所有61条评论

这是由编程错误引起的。 多次调用具有相同名称和架构的mongoose.model()connection.model()不会导致错误。 传递具有不同架构的模型的名称会产生错误。 实际上从未允许覆盖模型。

如果 mocha watch 重新需要您的架构并调用mongoose.model(preExistingName, newSchema)那么是的,您现在会收到错误消息。 以前在这种情况下,猫鼬根本没有编译新模型,它只会吞下编程错误并返回旧模型,这是错误的行为。

var schema = new Schema;
var A = mongoose.model('A', schema);
var B = mongoose.model('A', schema); // no error
var C = mongoose.model('A', new Schema); // previously unreported error

好吧,这是有道理的。 谢谢!

@aheckmann - 我也在打这个,但不确定我做错了什么。 下面是我如何设置它。 第一次干净的测试运行没问题,如果我编辑+保存文件,mocha 会触发--watch重新运行,我得到

OverwriteModelError: Cannot overwrite "User" model once compiled.

用户.js

var mongoose = require('mongoose'),
  Schema = mongoose.Schema,
  ObjectId = mongoose.SchemaTypes.ObjectId;

var userSchema = new Schema({
  name: { type: String }
}), 
  User;

// other virtual / static methods added to schema

User = mongoose.model('User', userSchema);
module.exports = User;

用户规范.js

var User = require('../models/user'),
  mongoose = require('mongoose');

mongoose.connection.on('error', function (err) {
  console.log(err);
}

mongoose.connection.on('open', function () {
  console.log('connected');
}

describe('User', function () {

  before(function (done) {
    mongoose.connect('mongodb://localhost/test');
    done();
  }

  after(function (done) {
    mongoose.disconnect(function () {
      console.log('disconnected');
      done();
    });
  });

  it('should test something', function (done) {
     // tests
     done();
  });
});

对于猫鼬测试,我们每次只创建新的连接,而且效果很好。

var db = mongoose.createConnection()

https://github.com/LearnBoost/mongoose/blob/master/test/common.js#L74 -L98

我也在看这个。 这是一个最小的例子:

https://github.com/j0ni/mongoose-strangeness

也许我错过了定义模型的正确方法......?

https://github.com/j0ni/mongoose-strangeness/blob/master/test.spec.js#L21

应该是var Example = connection.model('Example', ExampleSchema)

谢谢@aheckmann ,效果很好。

它可以工作 b/ca 为您的每个测试创建新连接,并且模型在每个连接中本地缓存。 另一种方法失败了 b/c 模型在每个测试的猫鼬模块级别进行编译,因此存在冲突。

@aheckmann - 尝试在Locomotive应用程序中测试我的模型时出现此错误。

基本上,每个测试文件都会在before()函数中启动 Locomotive 应用程序。 启动应用程序会创建一个 mongo db 连接并加载到我的所有模型中 - 这在我的 mongoose 初始化文件中(在应用程序启动时运行一次):

var mongoose = require("mongoose"),
    fs = require("fs");

module.exports = function() {

  // Connect to DB
  switch (this.env) {
    case 'development':
      mongoose.connect('mongodb://localhost/slipfeed');
      break;
    case 'test':
      mongoose.connect('mongodb://localhost/slipfeed');
      break;
    case 'production':
      mongoose.connect('mongodb://mongodb.example.com/prod');
      break;
  }

  // Load models  
  var app = this,
      modelPath = app.get('models');
  fs.readdirSync(modelPath).forEach(function(file) {
    var name = file.substr(0, file.lastIndexOf('.'));
    require(modelPath + '/' + name)(mongoose, app);
  });

}

在我的每个模型文件中,我基本上都是这样做的:

module.exports = function(mongoose, app) {
  var Schema = mongoose.Schema;
  var User = new Schema(...);
  app.User = mongoose.model('User', User);
});

在我的每个模型测试文件中,我都在做这样的事情:

var locomotive = require("locomotive"),
    app = new locomotive.Locomotive(),
    should = require("should");

describe( "User", function() {

  before( function (done) {
    app.boot( process.cwd(), 'test', function () {
      done();
    });
  });

  after( function (done) {
    mongoose.disconnect( function (err) {
      if (err) throw err;
      console.log('DISCONNECT')
      done();
    })
  });      

  ...tests go here

});

app.boot()部分只是启动服务器,它加载配置文件并通过初始化程序运行(它们只是包含各种代码的文件,例如启动数据库连接)。

但是在我的第一个测试文件完成后,mocha 尝试加载下一个文件,我会得到OverwriteModelError

我知道这可能与加载下一个测试文件时没有关闭连接有关,或者我可能以错误的方式初始化我的模型。

无论如何,我尝试将mongoose.disconnect到我的after()函数中,但这并没有帮助。

它与打开或关闭的连接无关。 您正在尝试重新定义已存在于不同模式的模型。

var a = new Schema({ x: 'string' });
var b = new Schema({ x: 'string' });
mongoose.model('Thingy', a);
mongoose.model('Thingy', a); // ok, a === the registered schema instance
mongoose.model('Thingy', b); // error a !== the registered schema

FWIW,我认为一些稍微不那么琐碎的示例代码可以防止这种情况重复出现。 也许一个定义单个模型的小型 express 应用程序以可注入的方式初始化连接,以及一个 mocha 集成测试(在我的情况下,我使用的是 supertest,但不管是什么),它在不触发此问题的情况下注入连接。

如果有一些应用程序您知道我们可以查看 @aheckmann ,并且您认为它实现了规范模式,那么就可以解决问题。

这是我采取的措施:

https://github.com/j0ni/beachenergy.ca/blob/master/datamodel/index.js

我不喜欢它,但它使问题消失。 如果我使用的是猫鼬反模式(似乎很可能),那么规范示例会有所帮助。

@aheckmann——我100%确定我没有创建一个具有不同模式的新模型。 我做了一个骨架/演示项目来说明这个问题: https :

make启动 mocha,第一次通过,测试将通过。 现在,编辑models/user.js并保存(例如 CTRL/CMD + S),mocha 使用--watch获取更改,我们点击OverwriteModelError

这是另一种选择......我使用nodemon。 这可确保每次测试运行时都会重新启动进程:
$ nodemon --exec "mocha -R min" 测试

那么推荐的解决方案是什么? 在每次测试之前创建一个新连接? 我们如何才能在我们的规范中定义 Mongoose 模型并且在观看时不会出错?

我放弃了单独用摩卡来解决它的尝试。 相反,我所做的是通过 nodemon 运行 mocha。 这样,节点进程重新启动,问题就消除了。

彼得·格拉夫
(从我的 iPad 发送,可能有拼写错误 :)
+1.512.784.3232
彼得。 [email protected]

2013 年 2 月 2 日上午 5:41,Oliver Joseph Ash [email protected]写道:

那么推荐的解决方案是什么? 在每次测试之前创建一个新连接? 我们如何才能在我们的规范中定义 Mongoose 模型并且在观看时不会出错?


直接回复此邮件或在 GitHub 上查看。

你是如何通过 nodemon 运行 mocha 的? 我试过这个,但我可能离得很远:

nodemon /usr/local/share/npm/bin/mocha --reporter spec --ui bdd --watch ./server/test/*.js

我用:
nodemon --exec "mocha -R min" 测试

鉴于您在下面显示的示例,您似乎使用规范而不是 min 并在服务器/测试中进行测试...如果是这样,只需更改参数...

2013 年 2 月 2 日上午 7:00,Oliver Joseph Ash [email protected]写道:

你是如何通过 nodemon 运行 mocha 的? 我试过这个,但我可能离得很远:

nodemon /usr/local/share/npm/bin/mocha --reporter spec --ui bdd --watch ./server/test/*.js

直接回复此邮件或在 GitHub 上查看。

好一个。 不如--watch快,但更重要的是,它有效。

我还注意到,当对 Mocha 测试所需的架构进行更改时,Mocha 不会在它们被缓存时重新加载它们:(

在2013年2月2日,在15:23,皮特格拉夫[email protected]写道:

我用:
nodemon --exec "mocha -R min" 测试

鉴于您在下面显示的示例,您似乎使用规范而不是 min 并在服务器/测试中进行测试...如果是这样,只需更改参数...

2013 年 2 月 2 日上午 7:00,Oliver Joseph Ash [email protected]写道:

你是如何通过 nodemon 运行 mocha 的? 我试过这个,但我可能离得很远:

nodemon /usr/local/share/npm/bin/mocha --reporter spec --ui bdd --watch ./server/test/*.js

直接回复此邮件或在 GitHub 上查看。


直接回复此邮件或在 GitHub 上查看。

@j0ni的解决方案对我来说效果很好。 我最终将所有对“mongoose.model”的调用替换为:

try {
    // Throws an error if "Name" hasn't been registered
    mongoose.model("Name")
} catch (e) {
    mongoose.model("Name", Schema)
}

我同意用这样的调用来乱扔代码并不好看。 最好有一个适用于最常见情况的简写,即模块只是在运行时重新解析而不更改架构。 也许称之为mongoose.getOrCreate(name, schema)

@yourcelf接受了您的建议并为我工作。 谢谢。

module.exports = getOrCreateModel('User', UserSchema);

还有另一种选择:从所有模型和模式中清除猫鼬。
在我的测试代码中,我添加了:

 mongoose.models = {};
 mongoose.modelSchemas = {};

它工作正常,至少对我来说。

@remicastaing完美,对我

@remicastaing 迄今为止最好的解决方法!

@remicastaing也对我

@remicastaing是的,看起来像一个

它仍然不知道实际问题是什么。

@remicastaing谢谢,也适用于我!

@remicastaing 也为我工作。 谢谢。

很抱歉划伤了旧痒,但我在尝试使用猫鼬运行 mocha -w 时遇到了同样的问题,尝试了所有可能的解决方案,但现在我遇到了麻烦。

我应该放在哪里

 mongoose.models = {};
 mongoose.modelSchemas = {};

?

之前,之后,之后,之前?

我应该在 beforeEach 上使用 mongoose.createConnection(),在 afterEach 上关闭吗?

我把

mongoose.models = {};
mongoose.modelSchemas = {};

紧随其后

var mongoose = require('mongoose');

在一个config.test.js直接在文件中的test文件夹( config.test.js唯一*.test.js在测试文件夹文件,其他*.test.js文件是在子文件夹)。 正如摩卡去递归( --recursive选项, mocha.opts通过测试文件夹),它开始于config.test.js

@remicastaing谢谢! 也为我工作(茉莉花)

只是另一种情况:
当我需要在代码和测试中使用不同字母大小写的模型时,我遇到了这个问题。
例如在规范中:
var User = require('./models/user');
并在代码中
var User = require('./models/User');
请记住,没有针对不同类型的缓存版本

+1 @asci 的经历。 如果您需要相同的模型但在两个不同的文件中使用不同的大小写,然后您需要另一个文件中的模型,则会引发“无法覆盖”错误

@iandoe我使用mongoose.connect并进行 mocha 测试

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

:+1::弓:

:+1:

如果要在beforeEach中创建模型,请先使用var db = mongoose.createConnection();创建一个新连接,然后使用db.model(...)而不是mongoose.model(...);中的beforeEach . 此行为是设计使然,而不是错误。

我有可以在运行时重新加载的代码,所以如果我在开发过程中更改架构或模型函数,我想覆盖现有模型。

除非有充分的理由不这样做,否则我不明白为什么我不能做这样的事情。

例子

delete mongoose.models['somemodel'];
var somemodel = db.mongoose.model('somemodel', someschema);

这对我有用,我已经这样做了大约一年。

或者也许有一种方法可以在不擦除现有模型/模式的情况下更改模式和模型功能,但我没有费心进一步调查。

:+1:

我知道这是旧的,但如果有人偶然发现这个,我通过制作一个这样的数据库测试实用程序文件来解决它:

// DatabaseUtils.js
import mongoose from 'mongoose';
const MONGO_URI = 'mongodb://localhost:27017/collectionname'

export function tearDown(){
  mongoose.models = {};
  mongoose.modelSchemas = {};
  resetDb();
  if(mongoose.connection.readyState) mongoose.disconnect();
}

export function setUp(){
  if(!mongoose.connection.readyState) mongoose.connect(MONGO_URI);
}

export function resetDb(){
  if(mongoose.connection.db) mongoose.connection.db.dropDatabase();
}

然后从您的测试文件中,您可以使用:

import { setUp, tearDown, resetDb } from './DatabaseUtils'

describe('Testing MongoDB models', function(){
  before(setUp)
  after(tearDown)
  afterEach(resetDb)

  it('Some test of a mongoose model', () =>  {
  // Test code here.
  }
})

这个 StackOverflow 答案是这样解决的:

let users
try { users = mongoose.model('users') }
catch (e) { users = mongoose.model('users', <UsersSchema...>) }

编辑:正如@DanielRamosAcosta所指出的,“问题是,如果您的架构发生更改,更改将不会生效。”

谢谢@remicastaing ,我已经失去了 5 分钟,直到我找到你的解决方案,应该更明显:)

谢谢@juanpabloaj ,这对我有用🎉

@gunar问题是,如果您的架构发生更改,更改将不会生效。

@juanpabloaj谢谢兄弟!

我只是使用,而不是创建一个丑陋的 try/catch:

let users = mongoose.models.users || mongoose.model('users', <UsersSchema...>)

这个讨论真的很丢人
mongoose 的实际作者给出了正确的解决方案( 12 ),但所有的赞许都指向了不正确的 hacky 解决方案。
真丢人

@tomyam1 ,也许是因为那些

有时,方便的解决方案比技术上正确的解决方案更有效:)

@tomyam1合作解决问题并没有什么可耻的,我感谢在这里发布自己的解决方案的每个人。 没有详细说明 - 在我的情况下,“不正确的hacky”建议之一效果很好,而您所谓的“正确”解决方案都没有。

互相帮助并不可耻。 感谢所有发表建设性帖子的人。

先生们(garethdown44,fega),那么我们不同意。
为避免制造更多垃圾邮件,请使用表情符号反应。

总结一下这个线程:

此页面中只有三种不同的解决方案:

  1. 使用缓存模型(如果存在):
mongoose.models.users || mongoose.model('users', <UsersSchema...>)

或者

try {
    // Throws an error if "Name" hasn't been registered
    mongoose.model("Name")
} catch (e) {
    mongoose.model("Name", Schema)
}

这不是解决方案,因为它不会在模型中进行更改。

  1. 清除模型缓存
 mongoose.models = {};
 mongoose.modelSchemas = {};

尝试了这个解决方案,但没有用,谁知道为什么?
它依赖于修改未记录的内部变量。
它在 2013 年提出时可能确实有效,但已经是 4 年后的事了。

  1. 在连接上保存模型。
    这是mogoose作者提出的官方解决方案。
    然而,它几乎完全被忽视了。 对我来说,这是可耻的。
const Mongoose = require('mongoose');
const DB = Mongoose.createConnection(...);
const Model = DB.model("Name", schema);

@tomyam1 - 原则上同意你所说的。 然而,公平地说,该解决方案确实是在另一个(hacky)解决方案之后两年出现的!!

就我而言,我选择了 nodemon 解决方案。 当我意识到发生了什么以及测试代码的结构方式时,它需要进行大量重构,而我没有时间去做。 我很高兴提出了 nodemon 解决方案并且人们对它进行了 +1。 这就是我要说的。

可悲的是,Nodemon 重新启动了整个测试套件。 mocha --watch重新加载速度要快得多

可悲的是,Nodemon 重新启动了整个测试套件,因此 mocha --watch 的重新加载速度要快得多

缓慢的重新加载和理智的开发人员是一种权衡,我将在测试运行中过度切片毫秒(或秒)。

此外,正如这里没有提到的, npm-watch是一个执行此操作的工具(它包装了 nodemon),因此您可以定义一个简单的 npm 脚本watch https://www.npmjs。 com/package/npm-watch

我有同样的问题,所以我检查mongoose.modelNames()并确定我是编译模型还是只检索已经编译的模型,因为 mocha --watch 会导致这个问题。 所以这里是代码:

mongoose.modelNames().indexOf("User") === -1 //if my model has not been compiled...
 ? mongoose.model("User", UserSchema) //...compile model and return it
 : mongoose.connection.model("User"); //else it is already compiled, so return this model

现在你将它作为一个函数返回(用参数替换“User”,用你的架构的参数替换 UserSchema)为module.exports并且当你需要它时你调用这个函数。

https://stackoverflow.com/a/49806603/5674976

@remicastaing 很有魅力,谢谢!

在某些特殊实例上,可能需要清除models引用

after((done) => {
  Object.keys(mongoose.models).forEach((k) => {
    delete mongoose.models[k];
  });
  Object.keys(mongoose.modelSchemas).forEach((k) => {
    delete mongoose.modelSchemas[k];
  });
  mongoose.connection.close();
  done();
});

mocha --watch 不需要describe之外的任何东西,即不会重新定义您的架构。

与数据库建立最少连接的最有效、最优雅且易于理解的测试方法是在测试套件之外设置连接、模式和模型。

与我在每次测试之前设置模型的其他示例相比,这段代码非常枯燥(对我来说不是必需的)。

以下测试套件有效:

const expect = require("chai").expect;
const mongoose = require("mongoose"),
  UserSchema = require("../data/models/User");
const connection = mongoose.createConnection(
  process.env.MONGO_URL || "mongodb://127.0.0.1/test"
);
const User = connection.model("User", UserSchema);

describe("Database Testing", function() {
  it("MongoDB is working and repeatedly testable", function(done) {
    let user = User({
      username: "My user"
    });

    user
      .save()
      .then(doc => {
        console.log(doc); // This outputs the doc.
        expect(doc.username).to.equal("My user");
        done();
      })
      .catch(err => {
        console.error(err);
        expect(err).to.be.null;
        done();
      });
  });
});

../data/models/User.js

let mongoose = require("mongoose");

let UserSchema = new mongoose.Schema({
  username: String
});
module.exports = UserSchema; // 

我曾经对 MongoDb 如何建立它的连接、模式和模型感到困惑。 我没有意识到您可以定义一个模型并多次使用它(显然,但是......)

我完全遵循文档并制作了一个定义架构并返回模型的模块。

这意味着如果我在测试中需要它,架构将被重新定义多次,因为它一直需要定义架构的模块。 Schema 的多重定义是没有道理的。

人们的解决方案(虽然有效,但效率较低)让您在每次测试之前创建一个新连接,并且通常在每次测试之前创建一个新模型。

但一个简单的理解是:

  • mocha --watch 不会多次执行describe之外的任何内容
  • 您可以针对不同的事物多次使用相同的连接、架构和模型。

有效而优雅地解决了这个问题。

下面是我让这个工作效率较低的原始方法。

./test/db.spec.js

const expect = require("chai").expect;
const mongoose = require("mongoose"),

  // mocha --watch does not rerequire anything
  // outside of the test suite ("describe").
  // Define your Schemas and models separately, so
  // that you can define your Schema once above
  // your test suites, and your models many times
  // when testing. 
  UserSchema = require("../data/models/User");

describe("mongoose strangeness", function() {
  var connection = mongoose.createConnection(
    process.env.MONGO_URL || "mongodb://127.0.0.1/test"
  );

  // I tried the beforeEach and afterEach, but this wasn't needed:

  // beforeEach(function(done) {
  //   connection = mongoose.createConnection(
  //     process.env.MONGO_URL || "mongodb://127.0.0.1/test"
  //   );
  //   connection.once("open", function() {
  //     done();
  //   });
  // });

  // afterEach(function(done) {
  //   connection.close(function() {
  //     done();
  //   });
  // });

  it("MongoDB testable", function(done) {
    let User = connection.model("User", UserSchema);

    let user = User({
      username: "My user"
    });

    user
      .save()
      .then(doc => {
        console.log(doc); // This outputs the doc every time.
        expect(doc.username).to.equal("My user");
        done();
      })
      .catch(err => {
        console.error(err);
        expect(err).to.be.null;
        done();
      });
  });
});

快乐的 MongoDbing。

这段代码对我有用:

if (mongoose.modelNames().includes('Model')) {
    mongoose.deleteModel('Model');
}

mongoose.model('Model', new mongoose.Schema({ ... }));

@verheyenkoen是对的。 这是 deleteModel 文档的链接: https: //mongoosejs.com/docs/api/connection.html#connection_Connection -deleteModel

此页面是否有帮助?
0 / 5 - 0 等级