如何创建一个可模拟的类来连接到mongoDB?

我尝试创建一个类来连接到mongoDB(并使用( gridfs-stream )获得一个gridFS连接。但是,我得到两个问题:

  • 我确实有时会server instance in invalid state connected的mongo错误server instance in invalid state connected
  • 我不可能嘲笑这门课 - 用jestJS
  • 所以,如果有人能够帮助我优化这个班级以获得一个非常扎实的工作课程,我将非常感激。 例如,我不喜欢connect()函数中的let that = this

    示例回购

    DB类

    const mongo = require('mongodb')
    const Grid = require('gridfs-stream')
    const { promisify } = require('util')
    
    export default class Db {
      constructor (uri, callback) {
        this.db = null
        this.gfs = null
        const server = process.env.MONGO_SERVER || 'localhost'
        const port = process.env.MONGO_PORT || 27017
        const db = process.env.MONGO_DB || 'test'
    
        // Is this the correct way to connect (using mongo native driver)?
        this.connection = new mongo.Db(db, new mongo.Server(server, port))
        this.connection.open = promisify(this.connection.open)
        this.connected = false
        return this
      }
    
      async connect (msg) {
        let that = this
        if (!this.db) {
          try {
            await that.connection.open()
            that.gfs = Grid(that.connection, mongo)
            this.connected = true
          } catch (err) {
            console.error('mongo connection error', err)
          }
        }
        return this
      }
    
      isConnected () {
        return this.connected
      }
    }
    

    这个函数将使用上面的类为数据库添加一个新用户:

    import bcrypt from 'bcrypt'
    import Db from './lib/db'
    const db = new Db()
    
    export async function createUser (obj, { username, password }) {
      if (!db.isConnected()) await db.connect()
      const Users = db.connection.collection('users')
      return Users.insert({
        username,
        password: bcrypt.hashSync(password, 10),
        createdAt: new Date()
      })
    }
    

    单元测试

    我需要创建一个单元测试来测试是否调用了mongoDB方法。 没有测试方法的集成测试。 所以我需要模拟数据库连接,集合和插入方法。

    import bcrypt from 'bcrypt'
    import { createUser } from '../../user'
    
    import Db from '../../lib/db'
    const db = new Db()
    jest.mock('bcrypt')
    
    describe('createUser()', () => {
      test('should call mongoDB insert()', async () => {
        bcrypt.hashSync = jest.fn(() => SAMPLE.BCRYPT)
        // create somekind of mock for the insert method...
        db.usersInsert = jest.fn(() => Promise.resolve({ _id: '507f1f77bcf86cd799439011' }))
        await createUser({}, {
          username: 'username',
          password: 'password'
        }).then((res) => {
          // test if mocked insert method has been called
          expect(db.usersInsert).toHaveBeenCalled()
          // ... or better test for the returned promise value
        })
      })
    })
    

    有很多方法可以解决这个问题。 我将列出其中的几个

  • 使用Jest手动模拟模拟DB类。 如果你使用了太多的mongo函数,这可能会很麻烦。 但是由于你通过数据库类封装了大部分内容,所以它仍然可以管理
  • 使用嘲弄的mongo实例。 这个项目允许你模拟一个MongoDB并使用js文件保存数据
  • 使用内存中的mongodb
  • 使用实际的mongodb
  • 我将在这里展示第一个案例,这是你使用代码发布的,以及如何使其工作。 所以我们要做的第一件事就是将__mocks__/db.js文件更新到下面

    jest.mock('mongodb');
    const mongo = require('mongodb')
    var mock_collections = {};
    var connectError = false;
    var connected = false;
    export default class Db {
        constructor(uri, callback) {
            this.__connectError = (fail) => {
                connected = false;
                connectError = fail;
            };
    
            this.clearMocks = () => {
                mock_collections = {};
                connected = false;
            };
    
            this.connect = () => {
                return new Promise((resolve, reject) => {
                    process.nextTick(
                        () => {
                            if (connectError)
                                reject(new Error("Failed to connect"));
                            else {
                                resolve(true);
                                this.connected = true;
                            }
                        }
                    );
                });
            };
    
            this.isConnected = () => connected;
    
            this.connection = {
                collection: (name) => {
                    mock_collections[name] = mock_collections[name] || {
                        __collection: name,
                        insert: jest.fn().mockImplementation((data) => {
                            const ObjectID = require.requireActual('mongodb').ObjectID;
    
                            let new_data = Object.assign({}, {
                                _id: new ObjectID()
                            },data);
                            return new Promise((resolve, reject) => {
                                    process.nextTick(
                                        () =>
                                            resolve(new_data))
                                }
                            );
                        })
                        ,
                        update: jest.fn(),
                        insertOne: jest.fn(),
                        updateOne: jest.fn(),
                    };
                    return mock_collections[name];
                }
            }
        }
    
    }
    

    现在几个解释

  • jest.mock('mongodb'); 将确保任何实际的mongodb调用都被嘲弄
  • connected connectErrormock_collections是全局变量。 这样可以影响你的user.js加载的Db的状态。 如果我们不这样做,我们将无法在测试中控制嘲笑的Db
  • this.connect显示了如何返回承诺,以及如何在需要时模拟连接到数据库的错误
  • collection: (name) => {确保您对createUser和您的测试的调用可以获得相同的集合接口,并检查是否实际调用了模拟函数。
  • insert: jest.fn().mockImplementation((data) => {显示如何通过创建自己的实现来返回数据
  • const ObjectID = require.requireActual('mongodb').ObjectID; 展示了如何在早期嘲笑mongodb时获得实际的模块对象
  • 现在来测试部分。 这是更新的user.test.js

    jest.mock('../../lib/db');
    import Db from '../../lib/db'
    import { createUser } from '../../user'
    
    const db = new Db()
    
    describe('createUser()', () => {
      beforeEach(()=> {db.clearMocks();})
    
      test('should call mongoDB insert() and update() methods 2', async () => {
        let User = db.connection.collection('users');
        let user = await createUser({}, {
          username: 'username',
          password: 'password'
        });
        console.log(user);
        expect(User.insert).toHaveBeenCalled()
      })
    
        test('Connection failure', async () => {
            db.__connectError(true);
            let ex = null;
            try {
              await createUser({}, {
              username: 'username',
              password: 'password'
            })
          } catch (err) {
            ex= err;
          }
          expect(ex).not.toBeNull();
          expect(ex.message).toBe("Failed to connect");
        })
    })
    

    几个指针再次

  • jest.mock('../../lib/db'); 将确保我们的手动模拟被加载
  • let user = await createUser({}, {因为你正在使用async ,你不会用thencatch ,这是使用的点async功能。
  • db.__connectError(true); 将设置全局变量connectedfalseconnectError为true。 因此,当createUser在测试中被调用时,它将模拟连接错误
  • ex= err; ,看看我如何捕捉异常并取消预期的呼叫。 如果你确实期望在catch块本身,那么当没有引发异常时,测试仍然会通过。 这就是为什么我在try/catch块之外进行异常测试的原因
  • 现在通过运行npm test来测试它,我们得到了

    Jest测试结果

    所有这一切都致力于低于你分享的回购

    https://github.com/jaqua/mongodb-class


    您正在存储一个DB实例,而不是实际的DB类。 另外,我在代码db.usersInsert不到db.usersInsert方法。 我们不能为您编写代码,但我可以指出您正确的方向。 另外,我不使用Jest,但Sinon的概念是一样的。 在你的情况下,我相信最好的办法是将类方法的原型存根出来,以返回你正在与之交互的对象。

    像这样的东西:

    // db.js
    export default class Db {
      getConnection() {}
    }
    
    // someOtherFile.js
    import Db from './db';
    
    const db = new Db();
    export default async () => {
      const conn = await db.getConnection();
      await connection.collection.insert();
    }
    
    // spec file
    import {
      expect
    } from 'chai';
    import {
      set
    } from 'lodash';
    import sinon from 'sinon';
    import Db from './db';
    import testFn from './someOtherFile';
    
    
    describe('someOtherFile', () => {
      it('should call expected funcs from db class', async () => {
        const spy = sinon.spy();
        const stub = sinon.stub(Db.prototype, 'getConnection').callsFake(() => {
          return set({}, 'collection.insert', spy);
        });
        await testFn();
        sinon.assert.called(spy);
      });
    });
    
    链接地址: http://www.djcxy.com/p/41439.html

    上一篇: How to create a mockable class to connect to mongoDB?

    下一篇: Indexing and percolating documents with elasticsearch