如何为mongoDB方法模拟动态?

时间:2017-12-29 12:38:47

标签: javascript unit-testing jestjs

在我的应用程序代码中有几个地方,我必须连接到数据库并获取一些数据。 对于我的单元测试(我正在使用JestJS),我需要嘲笑它。

让我们假设这个简单的异步函数:

/getData.js

    @POST
    @Consumes({ MediaType.APPLICATION_JSON})
    @Produces({ MediaType.APPLICATION_JSON})
    @Path("add")
    public Response addSuggestionAndFeedback(@Context UriInfo uriInfo, Student student) {

            System.out.println(uriInfo.getAbsolutePath());

      .........
    }

数据库连接位于单独的文件中:

/lib/db.js

import DB from './lib/db'

export async function getData () {
  const db = DB.getDB()
  const Content = db.get('content')
  const doc = await Content.findOne({ _id: id })
  return doc
}

你可以看到,我会收到数据库并获得一个收藏。在此之后,我将收到数据。

到目前为止我的模拟尝试:

/tests/getData.test.js

import monk from 'monk'

var state = {
  db: null
}

exports.connect = (options, done) => {
  if (state.db) return done()
  state.db = monk(
    'mongodb://localhost:27017/db',
    options
  )
  return state.db
}

exports.getDB = () => {
  return state.db
}

也许这不是最好的方法......?我很高兴每一次改进。

我的问题是在哪里放置数据库模拟,因为有多个测试,每个测试都需要import { getData } from '../getData' import DB from './lib/db' describe('getData()', () => { beforeEach(() => { DB.getDB = jest.fn() .mockImplementation( () => ({ get: jest.fn( () => ({ findOne: jest.fn(() => null) }) ) }) ) }) test('should return null', () => { const result = getData() expect(result).toBeNull() }) }) 调用的不同模拟结果。

也许有可能创建一个函数,可以使用所需的参数调用它。

1 个答案:

答案 0 :(得分:2)

首先,我只想指出,测试这个概念验证函数的价值似乎很低。你的代码真的没有;它是对DB客户端的所有调用。该测试基本上验证了,如果您模拟DB客户端返回null,则返回null。所以你真的只是测试你的模拟。

但是,如果您的函数在返回之前以某种方式转换数据将会很有用。 (虽然在这种情况下,我会使用自己的测试将变换放在自己的函数中,让我们回到我们开始的地方。)

因此,我建议一个解决方案可以执行您要求的,然后建议一个可以改进您的代码的解决方案。

不需要更改getData()的解决方案 - 不推荐:

您可以创建一个返回模拟的函数,该模拟提供一个findOne(),返回您指定的内容:

// ./db-test-utils
function makeMockGetDbWithFindOneThatReturns(returnValue) {
  const findOne = jest.fn(() => Promise.resolve(returnValue));
  return jest.fn(() => ({
    get: () => ({ findOne })
  }));
}

然后在您的代码文件中,在每个测试的beforeEach或之前调用DB.getDB.mockImplementation,并传递所需的返回值,如下所示:

import DB from './db';
jest.mock('./db');

describe('testing getThingById()', () => {
  beforeAll(() => {
    DB.getDB.mockImplementation(makeMockGetDbWithFindOneThatReturns(null));
  });

  test('should return null', async () => {
    const result = await getData();
    expect(result).toBeNull();
  });
});

使数据库相关代码更容易进行测试的解决方案

这个问题真的令人兴奋,因为它是每个功能只做一件事的价值的精彩例证!

getData似乎非常小 - 只有3行加return语句。所以乍一看它似乎做得太多了。

然而,这个微小的函数与DB的内部结构紧密耦合。它依赖于:

  • DB - 单身人士
  • DB.getDB()
  • DB.getDB().get()
  • DB.getDB().get().findOne()

这会产生一些负面影响:

  1. 如果DB改变了它的结构,因为它可以使用第三方组件,那么每个具有这些依赖关系的函数都会中断。
  2. 很难测试!
  3. 代码不可重复使用。因此,访问数据库的每个函数都需要调用getDB()db.get('collection'),从而导致重复的代码。
  4. 这是一种可以改善事物的方法,同时使你的测试模拟更加简单。

    导出db而不是DB

    我可能错了,但我的猜测是,每次使用DB时,您要做的第一件事就是致电getDB()。但是你只需要在整个代码库中进行一次调用。您可以从db而不是./lib/db.js导出DB,而不是在任何地方重复该代码:

    // ./lib/db.js
    const DB = existingCode(); // However you're creating DB now
    const dbInstance = DB.getDB();
    export default dbInstance;
    

    或者,您可以在启动函数中创建db实例,然后将其传递给DataAccessLayer类,该类将容纳所有数据库访问调用。再次只调用getDB()一次。这样就可以避免使用单例,这样可以简化测试,因为它允许依赖注入。

    添加帮助函数以获取数据库集合

    // ./lib/db.js
    const DB = existingCode(); // However you're creating DB now
    const dbInstance = DB.getDB();
    
    export function getCollectionByName(collectionName){
      return dbInstance.get(collectionName);
    }
    
    export default dbInstance;
    

    这个功能太琐碎了,似乎没必要。毕竟,它与它替换的代码具有相同的行数!但它消除了dbInstance(之前db)结构对调用代码的依赖性,同时记录了get()的作用(从名称中不明显)。

    现在,我getData重新命名getDocById以反映其实际效果,可能如下所示:

    import { getCollectionByName } from './lib/db';
    
    export async function getDocById(id) {
      const collection = getCollectionByName('things');
      const doc = await collection.findOne({ _id: id })
      return doc;
    }
    

    现在你可以分别从DB:

    模拟getCollectionByName
    // getData.test.js
    import { getDocById } from '../getData'
    import { getCollectionByName } from './lib/db'
    jest.mock('./lib/db');
    
    describe('testing getThingById()', () => {
      beforeEach(() => {
        getCollectionByName.mockImplementation(() => ({
          findOne: jest.fn(() => Promise.resolve(null))
        }));
      });
    
      test('should return null', async () => {
        const result = await getDocById();
        expect(result).toBeNull();
      });
    });
    

    这只是一种方法,可以采取更进一步的措施。例如,我们可以导出findOneDocById(collectionName, id)和/或findOneDoc(collectionName, searchObject),以使我们的模拟和findOne()调用更简单。