节点茉莉 – 如何unit testing函数涉及redis调用?

我刚刚开始玩茉莉花,我还在挣扎/嘲笑事情,例如,我有一个function

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

然后我开始写Jasmine规范

 describe('Test user helper', () => { let userInfo beforeEach(done => { userHelper.getUserInfo('userid123') .then(info => { userInfo = info done() }) }) it('return user info if user is found', () => { expect(userInfo).toEqual('info of userid 123') }) }) 

它运行良好,但我的问题是如何嘲笑redis.getAsync调用,所以它可以成为一个真正的孤立的unit testing?

谢谢。

好问题。 你可以嘲笑redis的依赖关系,但只有当你重写你的代码的时候,才能更好地testing。 在这里,这意味着使redis成为返回包含getUserInfo的对象的工厂的参数。

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

以下是可能的样子

用户助手/ 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); 

现在进行testing

 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) && `info of {id}`); } }; } describe('Test user helper', () => { const mockRedis = createMockRedis(); const userHelper = userHelperFactory(mockRedis); let userInfo; beforeEach(done => { userHelper.getUserInfo('userid123') .then(info => { userInfo = info; 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 ,但是我相信在module.exports.factory = factory这样做会less一些惯用的。 更广泛的一点是能够嘲笑testing,一般的可testing性只是参数化。

参数化非常强大,简单的说就是为什么开发人员用函数式语言来开发一些OOP程序员,比如真正的你,以及我们秘密的咒语,比如“哦光荣的dependency injection容器,给我一个X的instanceof ”:)

不是OOP或DI错误,而是可testing性,DI,IOC等仅仅是参数化。

有趣的是,如果我们将redis作为一个模块加载,并且如果使用可configuration的模块加载器,比如SystemJS,我们可以通过在testing级别使用加载器configuration来实现。 甚至Webpack也可以让你在一定程度上做到这一点,但是对于NodeJS来说,你需要猴子修补Require Function,或者创build一大堆假包,这不是很好的select。

给OP的具体回应

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

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