MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Подигравка/набиване Метод за запазване на модел Mongoose

Основи

При модулно тестване не трябва да се удря в DB. Мога да се сетя за едно изключение:натискане на DB в паметта, но дори това вече е в областта на интеграционното тестване, тъй като ще ви трябва само състоянието, запазено в паметта за сложни процеси (и следователно не наистина функционални единици). Така че, да, няма действителна база данни.

Това, което искате да тествате в модулните тестове, е дали вашата бизнес логика води до правилни API извиквания в интерфейса между вашето приложение и DB. Можете и вероятно трябва да приемете, че разработчиците на DB API/драйвера са свършили добра работа, тествайки дали всичко под API се държи според очакванията. Въпреки това, вие също искате да покриете във вашите тестове как вашата бизнес логика реагира на различни валидни резултати от API, като успешни запазвания, неуспехи поради съгласуваност на данните, неуспехи поради проблеми с връзката и т.н.

Това означава, че това, от което се нуждаете и искате да се подигравате, е всичко, което е под интерфейса на DB драйвера. Вие обаче ще трябва да моделирате това поведение, така че вашата бизнес логика да може да бъде тествана за всички резултати от извикванията на DB.

По-лесно е да се каже, отколкото да се направи, защото това означава, че трябва да имате достъп до API чрез технологията, която използвате, и трябва да познавате API.

Реалността на mongoose

Придържайки се към основите, ние искаме да се подиграем на повикванията, извършвани от основния „драйвер“, който mongoose използва. Ако приемем, че е node-mongodb-native трябва да се подиграваме на тези обаждания. Разбирането на пълното взаимодействие между mongoose и родния драйвер не е лесно, но обикновено се свежда до методите в mongoose.Collection защото последното разширява mongoldb.Collection и не повторно прилагане на методи като insert . Ако можем да контролираме поведението на insert в този конкретен случай, тогава знаем, че сме се подиграли с достъпа до DB на ниво API. Можете да го проследите в изходния код на двата проекта, този Collection.insert наистина е методът на оригиналния драйвер.

За вашия конкретен пример създадох публично Git хранилище с пълен пакет, но ще публикувам всички елементи тук в отговора.

Решението

Лично аз намирам "препоръчания" начин за работа с mongoose за доста неизползваем:моделите обикновено се създават в модулите, където са дефинирани съответните схеми, но те вече се нуждаят от връзка. За целите на наличието на множество връзки за общуване с напълно различни бази данни mongodb в един и същ проект и за целите на тестването това прави живота наистина труден. Всъщност, веднага щом притесненията са напълно разделени, mongoose, поне за мен, става почти неизползваем.

Така че първото нещо, което създавам, е файлът с описание на пакета, модул със схема и общ „генератор на модел“:

{
  "name": "xxx",
  "version": "0.1.0",
  "private": true,
  "main": "./src",
  "scripts": {
    "test" : "mocha --recursive"
  },
  "dependencies": {
    "mongoose": "*"
  },
  "devDependencies": {
    "mocha": "*",
    "chai": "*"
  }
}
var mongoose = require("mongoose");

var PostSchema = new mongoose.Schema({
    title: { type: String },
    postDate: { type: Date, default: Date.now }
}, {
    timestamps: true
});

module.exports = PostSchema;
var model = function(conn, schema, name) {
    var res = conn.models[name];
    return res || conn.model.bind(conn)(name, schema);
};

module.exports = {
    PostSchema: require("./post"),
    model: model
};

Такъв генератор на модели има своите недостатъци:има елементи, които може да се наложи да бъдат прикрепени към модела и би имало смисъл да ги поставите в същия модул, където е създадена схемата. Така че намирането на общ начин за добавяне на тези е малко трудно. Например, един модул може да експортира последващи действия, които да се изпълняват автоматично, когато се генерира модел за дадена връзка и т.н. (хакерство).

Сега нека се подиграем на API. Ще бъда прост и само ще се подигравам на това, което ми трябва за въпросните тестове. От съществено значение е, че бих искал да се подигравам с API като цяло, а не с отделни методи на отделни екземпляри. Последното може да е полезно в някои случаи или когато нищо друго не помага, но ще трябва да имам достъп до обекти, създадени вътре в моята бизнес логика (освен ако не са инжектирани или предоставени чрез някакъв фабричен модел), и това би означавало модифициране на основния източник. В същото време подиграването на API на едно място има недостатък:това е общо решение, което вероятно би приложило успешно изпълнение. За случаи на грешка при тестване може да се изисква подигравка в екземпляри в самите тестове, но тогава в рамките на вашата бизнес логика може да нямате директен достъп до екземпляра на напр. post създаден дълбоко в себе си.

И така, нека да разгледаме общия случай на подигравателно успешно извикване на API:

var mongoose = require("mongoose");

// this method is propagated from node-mongodb-native
mongoose.Collection.prototype.insert = function(docs, options, callback) {
    // this is what the API would do if the save succeeds!
    callback(null, docs);
};

module.exports = mongoose;

Като цяло, докато моделите се създават след модифицирането на mongoose е възможно горните подигравки да се правят на тестова база, за да се симулира всяко поведение. Не забравяйте обаче да се върнете към първоначалното поведение преди всеки тест!

И накрая, така могат да изглеждат нашите тестове за всички възможни операции за запазване на данни. Обърнете внимание, те не са специфични за нашата Post модел и може да се направи за всички други модели с точно същия макет на място.

// now we have mongoose with the mocked API
// but it is essential that our models are created AFTER 
// the API was mocked, not in the main source!
var mongoose = require("./mock"),
    assert = require("assert");

var underTest = require("../src");

describe("Post", function() {
    var Post;

    beforeEach(function(done) {
        var conn = mongoose.createConnection();
        Post = underTest.model(conn, underTest.PostSchema, "Post");
        done();
    });

    it("given valid data post.save returns saved document", function(done) {
        var post = new Post({
            title: 'My test post',
            postDate: Date.now()
        });
        post.save(function(err, doc) {
            assert.deepEqual(doc, post);
            done(err);
        });
    });

    it("given valid data Post.create returns saved documents", function(done) {
        var post = new Post({
            title: 'My test post',
            postDate: 876543
        });
        var posts = [ post ];
        Post.create(posts, function(err, docs) {
            try {
                assert.equal(1, docs.length);
                var doc = docs[0];
                assert.equal(post.title, doc.title);
                assert.equal(post.date, doc.date);
                assert.ok(doc._id);
                assert.ok(doc.createdAt);
                assert.ok(doc.updatedAt);
            } catch (ex) {
                err = ex;
            }
            done(err);
        });
    });

    it("Post.create filters out invalid data", function(done) {
        var post = new Post({
            foo: 'Some foo string',
            postDate: 876543
        });
        var posts = [ post ];
        Post.create(posts, function(err, docs) {
            try {
                assert.equal(1, docs.length);
                var doc = docs[0];
                assert.equal(undefined, doc.title);
                assert.equal(undefined, doc.foo);
                assert.equal(post.date, doc.date);
                assert.ok(doc._id);
                assert.ok(doc.createdAt);
                assert.ok(doc.updatedAt);
            } catch (ex) {
                err = ex;
            }
            done(err);
        });
    });

});

Важно е да се отбележи, че все още тестваме функционалността на много ниско ниво, но можем да използваме същия подход за тестване на всяка бизнес логика, която използва Post.create или post.save вътрешно.

Най-накрая, нека да изпълним тестовете:

> [email protected] test /Users/osklyar/source/web/xxx
> mocha --recursive

Post
  ✓ given valid data post.save returns saved document
  ✓ given valid data Post.create returns saved documents
  ✓ Post.create filters out invalid data

3 passing (52ms)

Трябва да кажа, че не е забавно да го правим по този начин. Но по този начин това е наистина чисто модулно тестване на бизнес логиката без никакви DB в паметта или реални и доста общо.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Коригирайте „дължина/ширина е извън границите“ в MongoDB при създаване на индекс на 2dsphere

  2. MongoDB агрегатен селективен проект

  3. Защо тази заявка за актуализиране актуализира само един запис веднъж

  4. Mongoose множество връзки

  5. Мангуста, сортирайте заявката по попълнено поле