Mongoose: OverwriteModelError com mocha 'relógio'

Criado em 14 dez. 2012  ·  61Comentários  ·  Fonte: Automattic/mongoose

Acabei de atualizar do 3.4.0 para o 3.5.1, e ao 'assistir' os testes do Mocha em um modelo, toda vez que o arquivo é recarregado, recebo um OverwriteModelError ao 'exigir' novamente - e eu acho, redefinir - o modelo.

Deve haver algum valor em tornar uma 'substituição' um erro, mas estou voltando para 3.4.0 por agora porque isso é muito chato.

Comentários muito úteis

Existe outra opção: limpar o mangusto de todos os modelos e esquemas.
No meu código de teste, adicionei:

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

E funciona bem, pelo menos para mim.

Todos 61 comentários

Isso é causado por um erro de programação. Chamar mongoose.model() ou connection.model() com o mesmo nome e esquema várias vezes não resulta em erro. Passar o nome de um modelo que existe com um esquema diferente produz o erro. A substituição de modelos nunca foi permitida.

Se o relógio mocha reexigir seu esquema e chamar mongoose.model(preExistingName, newSchema) , sim, você receberá um erro agora. Anteriormente neste cenário, o mongoose não estava compilando um novo modelo, ele apenas engoliria o erro de programação e retornaria o modelo antigo, que é um comportamento errado.

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, isso faz sentido. Obrigado!

@aheckmann - também estou acertando isso, e não tenho certeza do que estou fazendo de errado. abaixo está como eu configurei. o primeiro teste de limpeza está ok, se eu editar e salvar um arquivo, o mocha aciona uma nova execução com --watch e eu obtenho

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 testes de mangusto, apenas criamos novas conexões a cada vez e funciona bem.

var db = mongoose.createConnection()

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

Postado acompanhamento para grupos do google https://groups.google.com/d/topic/mongoose-orm/PXTjqqpaDFk/discussion

Eu também estou vendo isso. Aqui está um exemplo mínimo:

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

Talvez eu esteja perdendo a maneira certa de definir modelos ...?

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

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

Obrigado @aheckmann, isso funciona muito bem.

funciona b / ca nova conexão é criada para cada um de seus testes e o modelo é armazenado em cache localmente dentro da conexão para cada um. a outra forma falha, os modelos b / c estavam sendo compilados no nível do módulo mongoose para cada teste, daí os conflitos.

@aheckmann - Estou recebendo este erro ao tentar testar meus modelos em um aplicativo Locomotive .

Basicamente, cada arquivo de teste inicializa o aplicativo Locomotive na função before() . Iniciar o aplicativo cria uma conexão mongo db e carrega em todos os meus modelos - isso está no meu arquivo inicializador mongoose (é executado uma vez na inicialização do aplicativo):

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

}

Em cada um dos meus arquivos de modelo, estou basicamente fazendo o seguinte:

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

E em cada um dos meus arquivos de teste de modelo, estou fazendo algo assim:

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

});

A parte app.boot() apenas inicia o servidor, carrega o arquivo de configuração e executa os inicializadores (que são apenas arquivos que contêm vários pedaços de código, por exemplo, iniciar conexões db).

Mas depois que meu primeiro arquivo de teste terminar, e o mocha tentar carregar o próximo arquivo, vou pegar OverwriteModelError .

Eu entendo que provavelmente tem algo a ver com conexões não sendo fechadas ao carregar o próximo arquivo de teste, ou talvez eu esteja inicializando meus modelos de uma maneira incorreta.

De qualquer forma, tentei adicionar mongoose.disconnect às minhas funções after() , mas não adiantou.

não tem nada a ver com conexões abertas ou fechadas. você está tentando redefinir um modelo que já existe para um 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, acho que algum código de exemplo um pouco menos trivial impediria que isso ocorresse repetidamente. Talvez um pequeno aplicativo expresso, que define um único modelo, inicialize a conexão de uma forma que é injetável, e um teste de integração mocha (no meu caso estou usando superteste, mas seja o que for) que injeta a conexão sem acionar esse problema.

Se houver algum aplicativo que você conheça @aheckmann e que possamos resolverá o problema.

Aqui está o que eu recorri:

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

Não gosto disso, mas faz com que o problema desapareça. Se eu estiver usando um antipadrão de mangusto (parece provável), um exemplo canônico ajudaria.

Olá @aheckmann - Tenho 100% de certeza de que não estou criando um novo modelo com um esquema diferente. Fiz um esqueleto / projeto de demonstração para ilustrar esse problema: https://github.com/davisford/mongoose-test

Inicie o mocha com make e, da primeira vez, os testes serão aprovados. Agora, edite models/user.js e salve (por exemplo, CTRL / CMD + S), e mocha pega a mudança com --watch , e atingimos OverwriteModelError .

Aqui está outra opção ... Eu uso o nodemon. Isso garante que o processo seja reiniciado sempre que o teste for executado:
teste $ nodemon --exec "mocha -R min"

Então, qual é a solução recomendada para isso? Criar uma nova conexão antes de cada teste? De que outra forma podemos definir um modelo Mongoose em nossas especificações e não ter erros enquanto assistimos?

Desisti de tentar resolver com mocha sozinho. Em vez disso, o que faço é executar o mocha via nodemon. Dessa forma, o processo do nó é reiniciado e o problema é eliminado.

Petter Graff
(enviado do meu iPad, provavelmente com erros de ortografia :)
+1.512.784.3232
petter. [email protected]

Em 2 de fevereiro de 2013, às 5h41, Oliver Joseph Ash [email protected] escreveu:

Então, qual é a solução recomendada para isso? Criar uma nova conexão antes de cada teste? De que outra forma podemos definir um modelo Mongoose em nossas especificações e não ter erros enquanto assistimos?

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

Como você executa o mocha via nodemon exatamente? Eu tentei isso, mas provavelmente estou longe:

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

Eu uso:
teste nodemon --exec "mocha -R min"

Dado o exemplo que você mostra abaixo, parece que você usa spec em vez de min e tem seus testes em servidor / teste ... Se sim, apenas altere os parâmetros ...

Em 2 de fevereiro de 2013, às 7h, Oliver Joseph Ash [email protected] escreveu:

Como você executa o mocha via nodemon exatamente? Eu tentei isso, mas provavelmente estou longe:

nodemon / usr / local / share / npm / bin / mocha --reporter spec --ui bdd --watch ./server/test/*.js
-
Responda a este e-mail diretamente ou visualize-o no GitHub.

Agradável. Não tão rápido quanto --watch , mas o mais importante, ele funciona.

Também percebi que quando são feitas alterações no esquema que o teste Mocha exige, o Mocha não os recarrega porque são armazenados em cache :(

Em 2 de fevereiro de 2013, às 15:23, Petter Graff [email protected] escreveu:

Eu uso:
teste nodemon --exec "mocha -R min"

Dado o exemplo que você mostra abaixo, parece que você usa spec em vez de min e tem seus testes em servidor / teste ... Se sim, apenas altere os parâmetros ...

Em 2 de fevereiro de 2013, às 7h, Oliver Joseph Ash [email protected] escreveu:

Como você executa o mocha via nodemon exatamente? Eu tentei isso, mas provavelmente estou longe:

nodemon / usr / local / share / npm / bin / mocha --reporter spec --ui bdd --watch ./server/test/*.js
-
Responda a este e-mail diretamente ou visualize-o no GitHub.

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

A solução de @j0ni funcionou bem para mim. Acabei substituindo todas as chamadas para "mongoose.model" por:

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

Concordo que não é bonito ter que encher o código de alguém com essas chamadas. Seria bom ter um atalho para isso que funcione para o caso mais comum, que é um módulo simplesmente ser analisado novamente em tempo de execução sem nenhuma alteração no esquema. Talvez chame de algo como mongoose.getOrCreate(name, schema) .

@yourcelf aceitou sua sugestão e funcionou para mim. Obrigado.

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

Existe outra opção: limpar o mangusto de todos os modelos e esquemas.
No meu código de teste, adicionei:

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

E funciona bem, pelo menos para mim.

@remicastaing Perfeito, funciona para mim. Obrigado!

@remicastaing melhor solução alternativa até agora!

@remicastaing também funcionou para mim, parece ser a melhor solução até agora

@remicastaing sim, parece uma solução de trabalho.

Ele ainda não sabe qual é o problema real.

@remicastaing Obrigado, funciona para mim também!

@remicastaing Trabalhando para mim também. obrigado.

Desculpe coçar, mas estou enfrentando o mesmo problema ao tentar executar o mocha -w com o mongoose, tentei todas as soluções possíveis, mas agora estou batendo na parede.

onde devo colocar

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

?

beforeEach, afterEach, after, before?

Devo mongoose.createConnection () em beforeEach, fechar afterEach?

eu ponho o

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

logo depois

var mongoose = require('mongoose');

em um arquivo config.test.js diretamente na pasta test ( config.test.js o único *.test.js arquivo na pasta de teste, outros *.test.js arquivos estão em subpastas). Conforme o mocha vai recursivamente (opção --recursive em mocha.opts ) através da pasta de teste, ele começa com config.test.js .

@remicastaing obrigado! Trabalho para mim também (jasmim)

Apenas outro caso:
Eu tive esse problema, quando solicitei um modelo com letras maiúsculas e minúsculas no código e no teste.
Por exemplo, nas especificações:
var User = require('./models/user');
e em código
var User = require('./models/User');
Lembre-se de que não havia versão em cache para diferentes tipos de digitação

+1 para o que @asci experimentou. Se você precisar do mesmo modelo, mas com um caso diferente em dois arquivos diferentes, e depois precisar daqueles em outro, ocorrerá o erro 'Não é possível sobrescrever'

@iandoe eu uso mongoose.connect e coloco no teste mocha

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

: +1:: arco:

: +1:

Se você deseja criar um modelo em beforeEach , crie uma nova conexão com var db = mongoose.createConnection(); e então db.model(...) ao invés de mongoose.model(...); em beforeEach . Esse comportamento é próprio do projeto, e não um bug.

Eu tenho um código que pode ser recarregado em tempo de execução, então quero substituir um modelo existente se eu fizer uma alteração no esquema ou nas funções do modelo durante o desenvolvimento.

A menos que haja uma boa razão para não o fazer, não vejo por que não deveria ser capaz de fazer algo assim.

Exemplo

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

Isso funciona para mim, eu tenho feito assim por cerca de um ano.

Ou talvez haja uma maneira de alterar o esquema e as funções do modelo sem limpar um modelo / esquema existente, mas não me preocupei em investigar mais.

: +1:

Eu sei que isso é antigo, mas se alguém se deparar com isso, resolvi criando um arquivo de utilitário de teste de banco de dados 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();
}

Então, a partir do seu arquivo de teste, você pode 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 resposta StackOverflow resolve assim:

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

Edit: Como apontado por @DanielRamosAcosta , "O problema é que se o seu esquema mudar, as mudanças não terão efeito."

Obrigado @remicastaing , perdi 5 minutos até chegar a sua solução, deve ser mais evidente :)

Obrigado @juanpabloaj , funcionou para mim 🎉

@gunar O problema é que se o seu Schema mudar, as mudanças não terão efeito.

@juanpabloaj Obrigado mano!

Eu simplesmente uso, em vez de criar um try / catch feio:

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

Essa discussão é realmente vergonhosa
Os verdadeiros autores do mongoose deram a solução correta ( 1 e 2 ), mas todos os polegares vão para soluções hacky incorretas.
Realmente vergonhoso

@ tomyam1 , talvez porque essas soluções hacky estão resolvendo nossos problemas sem problemas ...: smiley:

Às vezes, uma solução conveniente funciona melhor do que uma solução tecnicamente correta :)

@ tomyam1 Não há nada de vergonhoso em colaborar para resolver um problema e agradeço a todos que postaram sua própria opinião sobre uma solução aqui. Sem entrar em detalhes - no meu caso, uma das sugestões 'hacky incorretas' funcionou perfeitamente, e as duas soluções que você está chamando de 'corretas' não funcionaram.

Ajudar uns aos outros não é vergonhoso. Obrigado a todos que fizeram uma postagem construtiva.

Bem, senhores (garethdown44, fega), então discordamos.
Para evitar a criação de mais spam, use as reações de emojis.

Para resumir este tópico:

Existem apenas três soluções diferentes nesta página:

  1. Use o modelo em cache, se existir:
mongoose.models.users || mongoose.model('users', <UsersSchema...>)

ou

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

Esta não é uma solução porque não capta mudanças nos modelos.

  1. Limpe o cache do modelo
 mongoose.models = {};
 mongoose.modelSchemas = {};

Tentei essa solução e não funcionou, e quem sabe por quê?
Ele depende da modificação de variáveis ​​internas não documentadas.
Provavelmente funcionou quando foi proposto em 2013, mas já se passaram 4 anos.

  1. Salve o modelo na conexão.
    Esta é a solução oficial proposta pelos autores do mogoose.
    E ainda assim foi quase completamente esquecido. Para mim, isso é vergonhoso.
const Mongoose = require('mongoose');
const DB = Mongoose.createConnection(...);
const Model = DB.model("Name", schema);

@ tomyam1 - Concorde em princípio com o que você está dizendo. No entanto, para ser justo, essa solução veio dois anos depois da outra solução (hacky) !!

No meu caso, escolhi a solução nodemon. Quando percebi o que estava acontecendo e a forma como o código de teste foi estruturado, teria exigido uma grande refatoração que não tenho tempo para fazer. Estou feliz que a solução do nodemon foi proposta e que as pessoas a marcaram com +1. Isso é tudo que estou dizendo.

Infelizmente, o Nodemon reinicia todo o conjunto de testes. mocha --watch recarrega muito mais rápido

Infelizmente, o Nodemon reinicia todo o conjunto de testes, então o mocha --watch recarrega muito mais rápido

uma recarga lenta e um desenvolvedor são é a compensação que vou fazer ao cortar milissegundos (ou segundos) de uma execução de teste.

além disso, como não foi mencionado aqui, npm-watch é uma ferramenta para fazer isso (envolve o nodemon) para que você possa definir um script npm simples watch https: //www.npmjs. com / package / npm-watch

Eu tive o mesmo problema, então eu verifico mongoose.modelNames() e determino se compilo o modelo ou apenas recupero o modelo já compilado, pois mocha --watch causa esse problema. Então aqui está o 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

agora você retorna isso como uma função (substitua "User" por argumento e UserSchema pelo argumento de seu Schema) para module.exports e quando você precisar, chame esta função.

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

@remicastaing funciona

Em algumas instanciações especiais, pode ser necessário limpar as referências 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 não exige nada fora de describe , ou seja, não redefine seus esquemas.

A maneira mais eficiente, elegante e compreensível de testar que faz o menor número de conexões com o banco de dados é configurar sua conexão, esquemas e modelos fora de seu conjunto de testes.

Este código é muito SECO em comparação com meu outro exemplo de configuração de um modelo antes de cada teste (não é necessário para mim).

O seguinte conjunto de testes 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();
      });
  });
});

De ../data/models/User.js

let mongoose = require("mongoose");

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

Eu costumava ficar confuso sobre como o MongoDb fazia suas conexões, esquemas e modelos. Não sabia que era possível definir um modelo e usá-lo muitas vezes (obviamente, mas ...)

Segui exatamente a documentação e fiz um módulo que definiu o esquema e retornou um modelo.

Isso significava que se eu o solicitasse em um teste, o Schema seria redefinido várias vezes, porque ele continua exigindo o módulo que define o esquema. E múltiplas definições do Schema não são boas.

As soluções de pessoas (que funcionam, embora com menos eficiência) permitem que você crie uma nova conexão antes de cada teste e, muitas vezes, um novo modelo antes de cada teste.

Mas um simples entendimento de que:

  • mocha --watch não executa nada fora de describe mais de uma vez
  • você pode usar a mesma conexão, esquema e modelo várias vezes para coisas diferentes.

resolveu isso com eficiência e elegância.

Abaixo está minha maneira original e menos eficiente de fazer isso funcionar.

./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 mim:

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

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

@verheyenkoen está certo. Aqui está o link para os documentos deleteModel: https://mongoosejs.com/docs/api/connection.html#connection_Connection -deleteModel

Esta página foi útil?
0 / 5 - 0 avaliações