我刚开始在茉莉花周围玩耍,但我仍在努力探索/模拟东西,例如,我有一个功能
module.exports = (() => { .... function getUserInfo(id) { return new Promise((resolve, reject) => { redis.getAsync(id).then(result => { resolve(result) }) }) } return { getUserInfo: getUserInfo } })()
然后我开始编写茉莉花规格
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调用,这样它才能成为真正的隔离单元测试?
谢谢。
好问题。您可以模拟redis依赖关系,但前提是您稍微重写一下代码以使其更具可测试性。在这里,这意味着将redis设为返回包含的对象的工厂的参数getUserInfo
。
当然,这会更改API,调用者现在需要调用export来获取对象。为了解决这个问题,我们可以创建一个包装器模块,该包装器模块使用标准redis对象调用该函数,然后返回结果。然后,我们将实际工厂移至内部模块,该模块仍可以对其进行测试。
这可能是什么样子
user-helper / factory.js
module.exports = redis => { .... function getUserInfo(id) { return redis.getAsync(id); // note simplified as new Promise was not needed } return {getUserInfo}; };
user-helper / 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程序员(例如您的确如此),以及我们的秘密咒语,例如“哦光荣的Dependency Injection Container,给我一个instanceof
X” :)
不是说OOP或DI搞错了,而是因为可测试性,DI,IOC等只是关于参数化。
有趣的是,如果我们将redis作为模块进行加载,并且如果使用的是可配置的模块加载器(例如SystemJS),则可以通过在测试级别上简单地使用加载器配置来实现。甚至Webpack都可以在某种程度上允许您执行此操作,但是对于NodeJS,您将需要猴子修补Require Function或创建一堆伪造的软件包,这不是很好的选择。
对OP的具体回应
谢谢!这是个好主意,但实际上,当我要测试大量文件时,我需要为每个文件创建一个工厂和index.js,这似乎很奇怪。
您将需要重新构建API表面,并简单地导出消耗代码必须调用的工厂(而不是应用这些工厂的结果)来减轻负担,但是需要权衡取舍,默认实例对消费者很有帮助。