gpt4 book ai didi

node.js - Mocking/stubbing Mongoose 模型保存方法

转载 作者:IT老高 更新时间:2023-10-28 13:08:52 26 4
gpt4 key购买 nike

给定一个简单的 Mongoose 模型:

import mongoose, { Schema } from 'mongoose';

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

const Post = mongoose.model('Post', PostSchema);

export default Post;

我想测试这个模型,但我遇到了一些障碍。

我当前的规范看起来像这样(为简洁起见省略了一些内容):
import mongoose from 'mongoose';
import { expect } from 'chai';
import { Post } from '../../app/models';

describe('Post', () => {
beforeEach((done) => {
mongoose.connect('mongodb://localhost/node-test');
done();
});

describe('Given a valid post', () => {
it('should create the post', (done) => {
const post = new Post({
title: 'My test post',
postDate: Date.now()
});

post.save((err, doc) => {
expect(doc.title).to.equal(post.title)
expect(doc.postDate).to.equal(post.postDate);
done();
});
});
});
});

但是,有了这个,我每次运行测试时都会访问我的数据库,我宁愿避免这种情况。

我试过使用 Mockgoose ,但是我的测试将无法运行。
import mockgoose from 'mockgoose';
// in before or beforeEach
mockgoose(mongoose);

测试卡住并抛出一个错误说: Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.我试过将超时增加到 20 秒,但这并没有解决任何问题。

接下来,我扔掉了 Mockgoose 并尝试使用 Sinon stub save称呼。
describe('Given a valid post', () => {
it('should create the post', (done) => {
const post = new Post({
title: 'My test post',
postDate: Date.now()
});

const stub = sinon.stub(post, 'save', function(cb) { cb(null) })
post.save((err, post) => {
expect(stub).to.have.been.called;
done();
});
});
});

这个测试通过了,但不知何故对我来说没有多大意义。我对 stub , mock ,你有什么很陌生,......我不确定这是否是正确的方法。我在打 save方法在 post ,然后我断言它已被调用,但我显然是在调用它...此外,我似乎无法获得非 stub Mongoose 方法将返回的参数。我想比较 post变量 save方法返回,就像在我访问数据库的第一个测试中一样。我试过 couplemethods但他们都觉得很hackish。必须有一个干净的方式,不是吗?

几个问题:
  • 我真的应该避免像我一直在任何地方阅读的那样访问数据库吗?我的第一个示例运行良好,每次运行后我都可以清除数据库。然而,我真的觉得不太对劲。
  • 我将如何 stub Mongoose 模型中的 save 方法并确保它实际测试我想要测试的内容:将新对象保存到数据库。
  • 最佳答案

    基础知识

    在单元测试中,不应击中 DB。我可以想到一个异常(exception):访问内存数据库,但即使这已经存在于集成测试领域,因为您只需要为复杂进程保存在内存中的状态(因此不是真正的功能单元)。所以,是的,没有实际的数据库。

    您想要在单元测试中测试的是您的业务逻辑会在您的应用程序和数据库之间的接口(interface)上产生正确的 API 调用。您可以并且可能应该假设 DB API/驱动程序开发人员已经很好地测试了 API 下的所有内容都按预期运行。但是,您还希望在测试中涵盖您的业务逻辑如何对不同的有效 API 结果使用react,例如成功保存、由于数据一致性导致的失败、由于连接问题导致的失败等。

    这意味着您需要并想要模拟的是 DB 驱动程序接口(interface)下方的所有内容。但是,您需要对该行为进行建模,以便可以针对 DB 调用的所有结果测试您的业务逻辑。

    说起来容易做起来难,因为这意味着您需要通过您使用的技术访问 API,并且您需要了解 API。

    Mongoose 的现实

    坚持基础知识,我们想模拟 mongoose 使用的底层“驱动程序”执行的调用。假设它是 node-mongodb-native我们需要模拟这些调用。了解 mongoose 和 native 驱动程序之间的完整交互并不容易,但通常归结为 mongoose.Collection 中的方法。因为后者扩展 mongoldb.Collection并且不会重新实现类似 insert 的方法.如果我们能够控制 insert 的行为在这种特殊情况下,我们知道我们在 API 级别模拟了数据库访问。您可以在两个项目的源代码中对其进行跟踪,即 Collection.insert是真正的 native 驱动程序方法。

    对于您的特定示例,我创建了 a public Git repository有一个完整的包,但我会在答案中发布所有元素。

    解决方案

    就我个人而言,我发现使用 mongoose 的“推荐”方式非常不可用:模型通常在定义相应模式的模块中创建,但它们已经需要连接。为了让多个连接与同一项目中完全不同的 mongodb 数据库进行通信以及出于测试目的,这让生活变得非常艰难。事实上,一旦顾虑完全分离, Mongoose 就几乎无法使用,至少对我而言。

    所以我创建的第一件事是包描述文件,一个带有模式和通用“模型生成器”的模块:

    package.json


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

    src/post.js


    var mongoose = require("mongoose");

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

    module.exports = PostSchema;

    src/index.js


    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,而不是单个实例的单个方法,这一点至关重要。后者在某些情况下可能很有用,或者在没有其他帮助的情况下,但我需要访问在我的业务逻辑内部创建的对象(除非通过某种工厂模式注入(inject)或提供),这意味着修改主源。同时,在一个地方模拟 API 有一个缺点:它是一种通用的解决方案,可能会成功执行。对于测试错误情况,可能需要在测试本身的实例中进行模拟,但是在您的业务逻辑中,您可能无法直接访问例如 post在内心深处创造。

    那么,我们来看看mock成功API调用的一般情况:

    test/mock.js


    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之后创建模型,就可以想象上面的mock是在每个测试的基础上进行的,以模拟任何行为。但是,请确保在每次测试之前恢复到原始行为!

    最后,这就是我们对所有可能的数据保存操作的测试的样子。注意,这些不是我们 Post 特有的模型,并且可以为所有其他模型完成,并且具有完全相同的模拟。

    test/test_model.js


    // 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内部。

    最后一点,让我们运行测试:

    ~/source/web/xxx $ npm test


    > xxx@0.1.0 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)

    我必须说,这样做并不有趣。但这种方式实际上是对业务逻辑的纯单元测试,没有任何内存中或真实的数据库,并且相当通用。

    关于node.js - Mocking/stubbing Mongoose 模型保存方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33270667/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com