Mongoose: OverwriteModelError с mocha 'watch'

Созданный на 14 дек. 2012  ·  61Комментарии  ·  Источник: Automattic/mongoose

Я только что обновился с 3.4.0 до 3.5.1, и при «просмотре» тестов Mocha на модели каждый раз, когда файл перезагружается, я получаю OverwriteModelError из-за повторного «запроса» - и, я думаю, переопределения - модели.

Должна быть какая-то ценность в создании ошибки перезаписи, но сейчас я возвращаюсь к 3.4.0, потому что это слишком мучительно.

Самый полезный комментарий

Есть еще вариант: очистить мангуста от всех моделей и схем.
В моем тестовом коде я добавил:

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

И он отлично работает, по крайней мере, у меня.

Все 61 Комментарий

Это вызвано ошибкой программирования. Вызов mongoose.model() или connection.model() с одним и тем же именем и схемой несколько раз не приводит к ошибке. При передаче имени модели, существующей с другой схемой, возникает ошибка. Перезапись моделей никогда не разрешалась.

Если часы mocha повторно требуют вашу схему и вызывают 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 - я тоже --watch и я получаю

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

для тестов мангуста мы просто каждый раз создаем новые соединения, и это хорошо работает.

var db = mongoose.createConnection()

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

Размещено продолжение групп Google https://groups.google.com/d/topic/mongoose-orm/PXTjqqpaDFk/discussion

Я тоже это вижу. Вот минимальный пример:

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 / c компилировались на уровне модуля мангуста для каждого теста, отсюда и конфликты.

@aheckmann - Я получаю эту ошибку при попытке протестировать свои модели в приложении " Локомотив" .

По сути, каждый тестовый файл загружает приложение Locomotive в функции before() . Загрузка приложения создает соединение 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() просто запускает сервер, загружает файл конфигурации и запускает инициализаторы (которые представляют собой просто файлы, содержащие различные фрагменты кода, например запуск соединений с базой данных).

Но после того, как мой первый тестовый файл будет готов, и мокко попытается загрузить следующий файл, я получу 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, я думаю, что несколько менее тривиальный пример кода предотвратит повторение этого повторно. Возможно, крошечное экспресс-приложение, которое определяет одну модель, инициализирует соединение способом, который можно вводить, и тест интеграции мокко (в моем случае я использую супертест, но что угодно на самом деле), которое вводит соединение, не вызывая этой проблемы.

Если вы знаете какое- то приложение изучить и которое, как вы считаете, реализует канонический шаблон, это поможет.

Вот к чему я прибегал:

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

Мне это не нравится, но это решает проблему. Если я использую антипаттерн мангуста (кажется вероятным), мне может помочь канонический пример.

Привет @aheckmann - Я на 100% уверен, что не создаю новую модель с другой схемой. Я сделал скелет / демонстрационный проект, чтобы проиллюстрировать эту проблему: https://github.com/davisford/mongoose-test

Начните мокко с make и в первый раз тесты пройдут. Теперь отредактируйте models/user.js и сохраните (например, CTRL / CMD + S), и мокко подхватит изменение с помощью --watch , и мы нажмем OverwriteModelError .

Вот еще вариант ... Я использую nodemon. Это гарантирует, что процесс перезапускается каждый раз при запуске теста:
$ nodemon --exec "mocha -R min" test

Итак, какое решение для этого рекомендуется? Создавать новое соединение перед каждым тестом? Как еще мы можем определить модель Mongoose в наших спецификациях и избежать ошибок во время просмотра?

Я отказался от попыток решить эту проблему одним мокко. Вместо этого я запускаю mocha через nodemon. Таким образом, процесс узла перезапускается и проблема устраняется.

Петтер Графф
(отправлено с моего iPad, вероятны орфографические ошибки :)
+1.512.784.3232
петтер. [email protected]

2 февраля 2013 г. в 5:41 Оливер Джозеф Эш [email protected] написал:

Итак, какое решение для этого рекомендуется? Создавать новое соединение перед каждым тестом? Как еще мы можем определить модель Mongoose в наших спецификациях и избежать ошибок во время просмотра?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub.

Как именно запустить mocha через nodemon? Я пробовал это, но я, наверное, далеко:

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

Я использую:
nodemon --exec "mocha -R min" test

Учитывая пример, который вы показываете ниже, кажется, что вы используете spec, а не min, и ваши тесты находятся в server / test ... Если это так, просто измените параметры ...

2 февраля 2013 г. в 7:00 Оливер Джозеф Эш [email protected] написал:

Как именно запустить mocha через nodemon? Я пробовал это, но я, наверное, далеко:

nodemon / usr / local / share / npm / bin / mocha --reporter spec --ui bdd --watch ./server/test/*.js
-
Ответьте на это письмо напрямую или просмотрите его на GitHub.

Хороший. Не так быстро, как --watch , но, что более важно, работает.

Я также заметил, что когда в схему вносятся изменения, необходимые для теста Mocha, Mocha не будет перезагружать их, поскольку они кэшируются :(

2 февраля 2013 года в 15:23 Петтер Графф [email protected] написал:

Я использую:
nodemon --exec "mocha -R min" test

Учитывая пример, который вы показываете ниже, кажется, что вы используете spec, а не min, и ваши тесты находятся в server / test ... Если это так, просто измените параметры ...

2 февраля 2013 г. в 7:00 Оливер Джозеф Эш [email protected] написал:

Как именно запустить mocha через nodemon? Я пробовал это, но я, наверное, далеко:

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, пробовал все возможные решения, но теперь я натыкаюсь на стену.

куда я должен положить

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

?

beforeEach, afterEach, после, до?

Должен ли я mongoose.createConnection () для beforeEach, закрывать 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 и использую тест мокко

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

Это обсуждение действительно позорное
Фактические авторы мангуста дали правильное решение ( 1 и 2 ), но все упираются в неправильные хакерские решения.
Действительно постыдно

@ tomyam1 , может потому, что эти хакерские решения без проблем решают наши проблемы ...: smiley:

Иногда удобное решение работает лучше, чем технически правильное :)

@ tomyam1 Нет ничего постыдного в сотрудничестве для решения проблемы, и я благодарен всем, кто разместил здесь свой собственный взгляд на решение. Не вдаваясь в подробности - в моем случае одно из «неправильных хакерских» предложений сработало отлично, а оба решения, которые вы называете «правильными», - нет.

Помогать друг другу не постыдно. Спасибо всем, кто сделал конструктивный пост.

Итак, господа (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 - в принципе согласен с тем, что вы говорите. Однако, честно говоря, это решение появилось через два года после другого (хакерского) решения !!

В моем случае я выбрал решение nodemon. Когда я понял, что происходит, и то, как был структурирован тестовый код, потребовался большой рефакторинг, на который у меня нет времени. Я рад, что было предложено решение nodemon и что люди поставили ему +1. Это все, что я говорю.

К сожалению, Nodemon перезапускает весь набор тестов. mocha --watch перезагружается намного быстрее

К сожалению, Nodemon перезапускает весь набор тестов, поэтому mocha --watch перезагружается намного быстрее.

медленная перезагрузка и разумный разработчик - это компромисс, который я сделаю из-за сокращения миллисекунд (или секунд) от тестового прогона.

кроме того, как здесь не упоминалось, npm-watch - это инструмент для этого (он обертывает nodemon), поэтому вы можете определить простой скрипт npm watch https: //www.npmjs. com / пакет / npm-часы

У меня была такая же проблема, поэтому я проверяю 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 устанавливает свои соединения, схемы и модели. Я не понимал, что можно определить одну модель и использовать ее много раз (очевидно, но ...)

Я точно следил за документацией и создал модуль, который определил схему и вернул модель.

Это означало, что если бы мне это потребовалось в тесте, схему пришлось бы переопределять много раз, потому что она постоянно требует модуля, определяющего схему. И несколько определений схемы - не пустяк.

Народные решения (которые работают, хотя и менее эффективно) предполагают создание нового соединения перед каждым тестом и часто создание новой модели перед каждым тестом.

Но простое понимание того, что:

  • 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 рейтинги