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

use*_*695 7 javascript unit-testing node.js jestjs

我试图创建一个连接到mongoDB的类(并使用(gridfs-stream)获取gridFS连接.但是我确实遇到了两个问题:

  1. 我有时会得到mongo错误 server instance in invalid state connected
  2. 我不可能使用jestJS来模拟这个课程

所以如果有人能帮助我优化这门课程以获得一个非常扎实的工人阶级,我将非常感激.例如,我不喜欢let that = thisconnect()函数.

回购示例

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
  }
}
Run Code Online (Sandbox Code Playgroud)

此函数将使用上面的类向DB添加新用户:

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()
  })
}
Run Code Online (Sandbox Code Playgroud)

单元测试

我需要创建一个单元测试来测试是否调用了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
    })
  })
})
Run Code Online (Sandbox Code Playgroud)

Tar*_*ani 3

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

  • 使用 Jest 手动模拟来模拟 DB 类。如果您使用太多 mongo 函数,这可能会很麻烦。但由于您通过 DB 类封装了大部分内容,因此它仍然可以管理
  • 使用模拟的 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];
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

现在很少解释

  • jest.mock('mongodb');将确保任何实际的 mongodb 调用都会被嘲笑
  • connectedconnectErrormock_collections全局变量。这样我们就可以影响Dbuser.js加载的状态。如果我们不这样做,我们将无法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");
    })
})
Run Code Online (Sandbox Code Playgroud)

再次指点几句

  • jest.mock('../../lib/db');将确保我们的手动模拟被加载
  • let user = await createUser({}, {既然你正在使用async,你就不会使用thencatch。这就是使用函数的要点async
  • db.__connectError(true);会将全局变量设置connectedfalseconnectErrortrue。因此,当createUser在测试中被调用时,它将模拟连接错误
  • ex= err;,看看我如何捕获异常并取出 Expect 调用。如果您确实期望在catch块本身中,那么当没有引发异常时,测试仍然会通过。try/catch这就是为什么我在块外进行异常测试的原因

现在是通过运行来测试它的部分npm test,我们得到

笑话测试结果

所有这些都致力于您共享的以下存储库

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