Mongoose: OverwriteModelError con mocha 'watch'

Creado en 14 dic. 2012  ·  61Comentarios  ·  Fuente: Automattic/mongoose

Acabo de actualizar de 3.4.0 a 3.5.1, y cuando 'veo' las pruebas de Mocha en un modelo, cada vez que se vuelve a cargar el archivo, obtengo un OverwriteModelError al volver a 'requerir', y supongo, redefinir, el modelo.

Debe haber algún valor en hacer un error de 'sobrescritura', pero voy a volver a 3.4.0 por ahora porque esto es demasiado doloroso.

Comentario más útil

Hay otra opción: limpiar la mangosta de todos los modelos y esquemas.
En mi código de prueba, agregué:

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

Y funciona bien, al menos para mí.

Todos 61 comentarios

Esto se debe a un error de programación. Llamar a mongoose.model() o connection.model() con el mismo nombre y esquema varias veces no genera un error. Pasar el nombre de un modelo que existe con un esquema diferente produce el error. En realidad, nunca se permitió sobrescribir modelos.

Si mocha watch vuelve a requerir su esquema y llama a mongoose.model(preExistingName, newSchema) entonces sí, obtendrá un error ahora. Anteriormente, en este escenario, la mangosta no estaba compilando un nuevo modelo en absoluto, simplemente se tragaría el error de programación y devolvería el modelo anterior, que es un comportamiento incorrecto.

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

Ok, eso tiene sentido. ¡Gracias!

@aheckmann - también estoy golpeando esto, y no estoy seguro de qué estoy haciendo mal. A continuación se muestra cómo lo configuré. la primera ejecución de prueba limpia está bien, si edito + guardo un archivo, mocha activa una nueva ejecución con --watch y obtengo

OverwriteModelError: Cannot overwrite "User" model once compiled.

user.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;

userSpec.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();
  });
});

para las pruebas de mangosta, solo creamos nuevas conexiones cada vez y funciona bien.

var db = mongoose.createConnection()

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

Se publicó el seguimiento de los grupos de Google https://groups.google.com/d/topic/mongoose-orm/PXTjqqpaDFk/discussion

Yo también veo esto. Aquí hay un ejemplo mínimo:

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

¿Quizás me falta la forma correcta de definir modelos ...?

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

debe ser var Example = connection.model('Example', ExampleSchema)

Gracias @aheckmann que funciona muy bien.

funciona porque se crea una nueva conexión para cada una de sus pruebas y el modelo se almacena en caché localmente dentro de la conexión para cada una. la otra forma falla, los modelos b / c se estaban compilando en el nivel del módulo de mangosta para cada prueba, de ahí los conflictos.

@aheckmann - Recibo este error cuando intento probar mis modelos en una aplicación Locomotive .

Básicamente, cada archivo de prueba inicia la aplicación Locomotive en la función before() . Al iniciar la aplicación, se crea una conexión mongo db y se carga en todos mis modelos; esto está en mi archivo de inicialización de mangosta (se ejecuta una vez al iniciar la aplicación):

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);
  });

}

En cada uno de mis archivos de modelo, básicamente estoy haciendo esto:

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

Y en cada uno de mis archivos de prueba de modelo estoy haciendo algo como esto:

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

});

La parte app.boot() simplemente inicia el servidor, carga el archivo de configuración y se ejecuta a través de inicializadores (que son solo archivos que contienen varios fragmentos de código, por ejemplo, iniciar conexiones db).

Pero después de que termine mi primer archivo de prueba y mocha intente cargar el siguiente archivo, obtendré el OverwriteModelError .

Entiendo que probablemente tenga algo que ver con que las conexiones no se cierren al cargar el siguiente archivo de prueba, o quizás estoy inicializando mis modelos de una manera incorrecta.

De todos modos, intenté agregar mongoose.disconnect a mis funciones after() , pero eso no ayudó.

no tiene nada que ver con conexiones abiertas o cerradas. está intentando redefinir un modelo que ya existe en un esquema diferente.

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, creo que algún código de ejemplo un poco menos trivial evitaría que esto vuelva a aparecer repetidamente. Quizás una pequeña aplicación exprés, que define un solo modelo, inicializa la conexión de una manera que es inyectable, y una prueba de integración de moca (en mi caso estoy usando supertest, pero lo que sea en realidad) que inyecta la conexión sin desencadenar este problema.

Si hay alguna aplicación que conozca de @aheckmann que podamos ver y que considere que implementa un patrón canónico, eso funcionaría.

Esto es a lo que he recurrido:

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

No me gusta, pero hace que el problema desaparezca. Si estoy usando un anti-patrón de mangosta (parece probable), un ejemplo canónico ayudaría.

Hola @aheckmann : estoy 100% seguro de que no estoy creando un nuevo modelo con un esquema diferente. Hice un proyecto de esqueleto / demostración para ilustrar este problema: https://github.com/davisford/mongoose-test

Comience mocha con make y la primera vez, las pruebas pasarán. Ahora, edite models/user.js y guarde (por ejemplo, CTRL / CMD + S), y mocha recoge el cambio con --watch , y presionamos OverwriteModelError .

Aquí hay otra opción ... Yo uso nodemon. Esto asegura que el proceso se reinicia cada vez que se ejecuta la prueba:
$ nodemon --exec "mocha -R min" prueba

Entonces, ¿cuál es la solución recomendada para esto? ¿Crear una nueva conexión antes de cada prueba? ¿De qué otra manera podemos definir un modelo Mongoose en nuestras especificaciones y no tener un error mientras miramos?

Dejé de intentar resolverlo solo con moka. En cambio, lo que hago es ejecutar mocha a través de nodemon. De esta forma, se reinicia el proceso del nodo y se elimina el problema.

Petter Graff
(enviado desde mi iPad, es probable que haya errores ortográficos :)
+1.512.784.3232
petter. [email protected]

El 2 de febrero de 2013, a las 5:41 a. M., Oliver Joseph Ash [email protected] escribió:

Entonces, ¿cuál es la solución recomendada para esto? ¿Crear una nueva conexión antes de cada prueba? ¿De qué otra manera podemos definir un modelo Mongoose en nuestras especificaciones y no tener un error mientras miramos?

-
Responda a este correo electrónico directamente o véalo en GitHub.

¿Cómo se ejecuta mocha a través de nodemon exactamente? Intenté esto, pero probablemente estoy lejos:

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

Yo suelo:
nodemon --exec "mocha -R min" prueba

Dado el ejemplo que muestra a continuación, parece que usa spec en lugar de min y tiene sus pruebas en el servidor / prueba ... Si es así, simplemente cambie los parámetros ...

El 2 de febrero de 2013, a las 7:00 a. M., Oliver Joseph Ash [email protected] escribió:

¿Cómo se ejecuta mocha a través de nodemon exactamente? Intenté esto, pero probablemente estoy lejos:

nodemon / usr / local / share / npm / bin / mocha --reporter spec --ui bdd --watch ./server/test/*.js
-
Responda a este correo electrónico directamente o véalo en GitHub.

Buena esa. No tan rápido como --watch , pero lo que es más importante, funciona.

También noté que cuando se realizan cambios en el esquema que requiere la prueba de Mocha, Mocha no los recargará ya que están almacenados en caché :(

El 2 de febrero de 2013, a las 15:23, Petter Graff [email protected] escribió:

Yo suelo:
nodemon --exec "mocha -R min" prueba

Dado el ejemplo que muestra a continuación, parece que usa spec en lugar de min y tiene sus pruebas en el servidor / prueba ... Si es así, simplemente cambie los parámetros ...

El 2 de febrero de 2013, a las 7:00 a. M., Oliver Joseph Ash [email protected] escribió:

¿Cómo se ejecuta mocha a través de nodemon exactamente? Intenté esto, pero probablemente estoy lejos:

nodemon / usr / local / share / npm / bin / mocha --reporter spec --ui bdd --watch ./server/test/*.js
-
Responda a este correo electrónico directamente o véalo en GitHub.

-
Responda a este correo electrónico directamente o véalo en GitHub.

La solución de @ j0ni funcionó bien para mí. Terminé reemplazando todas las llamadas a "mongoose.model" con:

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

Estoy de acuerdo en que no es agradable tener que ensuciar el código con tales llamadas. Sería bueno tener una abreviatura para esto que funcione para el caso más común, que es que un módulo simplemente se vuelve a analizar en tiempo de ejecución sin cambios en el esquema. Tal vez lo llame algo así como mongoose.getOrCreate(name, schema) .

@yourcelf tomó su sugerencia y trabajó para mí. Gracias.

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

Hay otra opción: limpiar la mangosta de todos los modelos y esquemas.
En mi código de prueba, agregué:

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

Y funciona bien, al menos para mí.

@remicastaing Perfecto, me funciona. ¡Gracias!

¡@remicastaing es la mejor solución hasta ahora!

@remicastaing También funcionó para mí, parece ser la mejor solución hasta ahora

@remicastaing sí, parece una solución funcional.

Sin embargo, todavía no sabe cuál es el problema real.

@remicastaing Gracias, ¡también funciona para mí!

@remicastaing Trabajando para mí también. Gracias.

Perdón por rascarme una vieja picazón, pero estoy enfrentando el mismo problema al intentar ejecutar mocha -w con mangosta, probé todas las soluciones posibles, pero ahora estoy golpeando la pared.

donde debo poner

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

?

beforeEach, afterEach, after, before?

¿Debo mongoose.createConnection () en beforeEach, cerrar en afterEach?

Puse el

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

justo después de

var mongoose = require('mongoose');

en un archivo config.test.js directamente en la carpeta test ( config.test.js el único archivo *.test.js en la carpeta de prueba, otros archivos *.test.js están en subcarpetas). Como mocha va recursivamente (opción --recursive en mocha.opts ) a través de la carpeta de prueba, comienza con config.test.js .

@remicastaing gracias! Trabaja para mí también (jazmín)

Solo otro caso:
Tuve este problema, cuando necesitaba un modelo con diferentes letras mayúsculas en el código y en la prueba.
Por ejemplo en la especificación:
var User = require('./models/user');
y en codigo
var User = require('./models/User');
Solo tenga en cuenta que no había una versión en caché para diferentes tipos de escritura

+1 a lo que @asci experimentó. Si necesita el mismo modelo pero con un caso diferente en dos archivos diferentes, y luego los necesita en otro, arrojará el error 'No se puede sobrescribir'

@iandoe uso mongoose.connect y pongo la prueba de moca

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

: +1:: arco:

: +1:

Si desea crear un modelo en beforeEach , cree una nueva conexión con var db = mongoose.createConnection(); y luego db.model(...) en lugar de mongoose.model(...); en beforeEach . Este comportamiento es más por diseño que por error.

Tengo un código que se puede recargar en tiempo de ejecución, por lo que quiero sobrescribir un modelo existente si hago un cambio en el esquema o en las funciones del modelo durante el desarrollo.

A menos que haya una buena razón para no hacerlo, no veo por qué no debería poder hacer algo como esto.

Ejemplo

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

Esto funciona para mí, lo he estado haciendo de esa manera durante aproximadamente un año.

O tal vez hay una manera de cambiar el esquema y las funciones del modelo sin borrar un modelo / esquema existente, pero no me he molestado en investigar más.

: +1:

Sé que esto es antiguo, pero si alguien se encuentra con esto, lo resolví creando un archivo de utilidad de prueba de base de datos como este:

// 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();
}

Luego, desde su archivo de prueba, puede usar:

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.
  }
})

Esta respuesta de StackOverflow lo resuelve así:

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

Editar: Como lo señaló @DanielRamosAcosta , "El problema es que si su esquema cambia, los cambios no surtirán efecto".

Gracias @remicastaing , he perdido 5 minutos hasta que llegué a tu solución, debería ser más evidente :)

Gracias @juanpabloaj , eso funcionó para mí 🎉

@gunar El problema es que si su esquema cambia, los cambios no surtirán efecto.

@juanpabloaj ¡ Gracias hermano!

Simplemente uso, en lugar de crear un intento / captura feo:

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

Esta discusión es realmente vergonzosa
Los autores reales de la mangosta dieron la solución correcta ( 1 y 2 ), pero todos los pulgares se dirigen a soluciones piratas incorrectas.
Realmente vergonzoso

@ tomyam1 , tal vez porque esas soluciones hacky están resolviendo nuestros problemas sin ningún problema ...: smiley:

A veces, una solución conveniente funciona mejor que una técnicamente correcta :)

@ tomyam1 No hay nada de vergonzoso en colaborar para resolver un problema y estoy agradecido con todos los que han publicado aquí su propia opinión sobre una solución. Sin entrar en detalles, en mi caso, una de las sugerencias de 'hacky incorrecto' ha funcionado perfectamente, y las dos que usted llama soluciones 'correctas' no.

Ayudarnos unos a otros no es vergonzoso. Gracias a todos los que han hecho una publicación constructiva.

Bueno, señores (garethdown44, fega), entonces no estamos de acuerdo.
Para evitar crear más spam, utilice las reacciones de emojis.

Para resumir este hilo:

Solo hay tres soluciones diferentes en esta página:

  1. Use el modelo en caché si existe:
mongoose.models.users || mongoose.model('users', <UsersSchema...>)

o

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

Esta no es una solución porque no detectará cambios en los modelos.

  1. Limpiar la caché del modelo
 mongoose.models = {};
 mongoose.modelSchemas = {};

Probé esta solución y no funcionó, y ¿quién sabe por qué?
Se basa en la modificación de variables internas indocumentadas.
Probablemente funcionó cuando se propuso en 2013, pero han pasado 4 años.

  1. Guarde el modelo en la conexión.
    Esta es la solución oficial que propusieron los autores de mogoose.
    Y, sin embargo, se pasó por alto casi por completo. Para mí, esto es vergonzoso.
const Mongoose = require('mongoose');
const DB = Mongoose.createConnection(...);
const Model = DB.model("Name", schema);

@ tomyam1 - De acuerdo en principio con lo que estás diciendo. Sin embargo, para ser justos, esa solución llegó dos años después de la otra solución (hacky).

En mi caso elegí la solución nodemon. Cuando me di cuenta de lo que estaba sucediendo y de la forma en que estaba estructurado el código de prueba, habría requerido una gran refactorización que no tengo tiempo para hacer. Me alegra que se propusiera la solución de nodemon y que la gente hiciera +1 en ella. Eso es todo lo que digo.

Lamentablemente, Nodemon reinicia todo el conjunto de pruebas. mocha --watch tiene una recarga mucho más rápida

Lamentablemente, Nodemon reinicia todo el conjunto de pruebas, por lo que mocha --watch tiene una recarga mucho más rápida

una recarga lenta y un desarrollador sano es una compensación que haré sobre cortar milisegundos (o segundos) de una ejecución de prueba.

Además, como no se ha mencionado aquí, npm-watch es una herramienta para hacer esto (envuelve nodemon) para que pueda definir un script npm simple watch https: //www.npmjs. com / package / npm-watch

Tuve el mismo problema, así que verifico mongoose.modelNames() y determino si compilo el modelo o simplemente recupero el modelo ya compilado, ya que mocha --watch causa este problema. Entonces aquí está el código:

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

ahora devuelve esto como una función (reemplace "Usuario" con un argumento y UserSchema con el argumento de su esquema) por module.exports y cuando lo necesite, llame a esta función.

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

@remicastaing funciona a las

En algunas instancias especiales, puede ser necesario borrar las referencias 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 no requiere nada fuera de describe , es decir, no redefinirá sus esquemas.

La forma más eficiente, elegante y comprensible de probar que hace la menor cantidad de conexiones a la base de datos es configurar su conexión, esquemas y modelos fuera de su suite de prueba.

Este código es muy SECO en comparación con mi otro ejemplo de configuración de un modelo antes de cada prueba (no es necesario para mí).

El siguiente conjunto de pruebas funciona:

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();
      });
  });
});

Desde ../data/models/User.js

let mongoose = require("mongoose");

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

Solía ​​estar en un estado de confusión sobre cómo MongoDb hacía sus conexiones, esquemas y modelos. No me di cuenta de que se podía definir un modelo y usarlo muchas veces (obvio, pero ...)

Seguí la documentación exactamente e hice un módulo que definía el esquema y devolvía un modelo.

Esto significaba que si lo requería en una prueba, el esquema se redefiniría muchas veces, porque sigue requiriendo el módulo que define el esquema. Y las múltiples definiciones del esquema no son buenas.

Las soluciones de las personas (que funcionan, aunque de manera menos eficiente) hacen que cree una nueva conexión antes de cada prueba y, a menudo, un nuevo modelo antes de cada prueba.

Pero un simple entendimiento de que:

  • mocha --watch no ejecuta nada fuera de describe más de una vez
  • puede usar la misma conexión, esquema y modelo varias veces para diferentes cosas.

resuelto esto de manera eficiente y elegante.

A continuación se muestra mi forma menos eficiente y original de hacer que esto funcione.

./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();
      });
  });
});

Feliz MongoDbing.

Este código funciona para mí:

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

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

@verheyenkoen tiene razón. Aquí está el enlace a los documentos de deleteModel: https://mongoosejs.com/docs/api/connection.html#connection_Connection -deleteModel

¿Fue útil esta página
0 / 5 - 0 calificaciones