node jasmine - 如何对涉及redis调用的函数进行单元测试?

时间:2017-03-27 04:58:54

标签: javascript node.js unit-testing jasmine

我刚刚开始玩茉莉花,我仍在挣扎着狡猾/嘲弄的事情,例如,我有一个功能

import QtQuick.Window 2.2
import QtQuick.Controls 2.1

Popup {
    x: 100
    y: 100
    width: 200
    height: 300
    modal: true
    focus: true
    closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
    visible: false
}

然后我开始编写Jasmine规范

module.exports = (() => {
    ....

    function getUserInfo(id) {
        return new Promise((resolve, reject) => {
            redis.getAsync(id).then(result => {
                resolve(result)
            })
        }) 
    }
    return { getUserInfo: getUserInfo }
})()

它运行良好,但我的问题是如何模拟redis.getAsync调用,以便它可以成为一个真正的隔离单元测试?

感谢。

1 个答案:

答案 0 :(得分:4)

好问题。你可以模拟出redis依赖,但只有当你重写代码时,才会更加可测试。 这意味着将redis作为工厂的参数,返回包含getUserInfo的对象。

当然,这会改变API,调用者现在需要调用导出来获取对象。为了解决这个问题,我们可以创建一个使用标准redis对象调用函数的包装器模块,并返回结果。然后我们将实际的工厂移动到一个内部模块,它仍然允许它进行测试。

这可能是什么样的

用户辅助/ factory.js

module.exports = redis => {
  ....

  function getUserInfo(id) {
    return redis.getAsync(id); // note simplified as new Promise was not needed
  }
  return {getUserInfo};
};

用户辅助/ index.js

// this is the wrapper that preserves existing API
module.exports = require('./factory')(redis);

现在进行测试

const userHelperFactory = require('./user-helper/factory');

function createMockRedis() {
  const users = [
    {userId: 'userid123'},
    // etc.
  ];
  return {
    getAsync: function (id) {
      // Note: I do not know off hand what redis returns, or if it throws,
      // if there is no matching record - adjust this to match.
      return Promise.resolve(users.find(user => user.userId === id));
    }
  };
}

describe('Test user helper', () => {
  const mockRedis = createMockRedis();
  const userHelper = userHelperFactory(mockRedis);

  let userInfo;

  beforeEach(async done => {
    userInfo = await userHelper.getUserInfo('userid123');
    done();
  });

  it('must return user info when a matching user exists', () => {
    expect(userInfo).toEqual('info of userid 123');
  });
});

注意:正如评论中所讨论的,这只是我对手头情况的偶然方法。您可以使用许多其他设置和约定,但主要思想仅仅是基于IIFE结果的现有导出,这是一个可靠的模式,我利用NodeJS /index约定来保留现有的API。您也可以使用一个文件并通过module.exports = factory(redis)module.exports.factory = factory导出,但我认为,这在NodeJS中不那么惯用。更广泛的一点是,能够模拟测试,一般的可测试性只是参数化。

参数化非常强大,它的简单性就是为什么在函数式语言中工作的开发人员有时会嘲笑OOP程序员,比如你的真实,以及我们的秘密咒语,如“哦光荣的依赖注入容器,遗留给我instanceof X“:)

不是OOP或DI弄错了可测性,DI,IOC等只是参数化。

有趣的是,如果我们将redis作为模块加载,并且如果我们使用可配置的模块加载器,例如SystemJS,我们可以通过在测试级别使用加载器配置来实现。甚至Webpack也可以让你在某种程度上做到这一点,但是对于NodeJS,你需要修补需求函数,或者创建一堆伪包,这不是很好的选择。

OP的具体回复

  

谢谢!这是一个好主意,但实际上,当我需要测试大量文件时,我需要为每个文件创建一个工厂和index.js,这似乎很奇怪。

您需要重新构建API表面并简单地导出消耗代码必须调用的工厂,而不是应用这些工厂的结果,以减轻负担,但是存在权衡和默认实例对消费者有帮助。