我正在尝试使用RxJS和函数组合将使用经典OO Javascript编写的库重写为更具功能性和反应性的方法。我已经开始使用以下两个易于测试的函数(我跳过了Observables的导入):
创建-connection.js
export default (amqplib, host) => Observable.fromPromise(amqplib.connect(host))
创建-channel.js
export default connection => Observable.fromPromise(connection.createChannel())
我需要做的就是测试它们是注入amqplib或连接的模拟,并确保调用正确的方法,如下所示:
import createChannel from 'create-channel';
test('it should create channel', t => {
const connection = { createChannel: () => {}};
const connectionMock = sinon.mock(connection);
connectionMock.expects('createChannel')
.once()
.resolves('test channel');
return createChannel(connection).map(channel => {
connectionMock.verify();
t.is(channel, 'test channel');
});
});
所以现在我想将这两个函数放在一起:
import amqplib from 'amqplib';
import { createConnection, createChannel } from './';
export default ({ host }) =>
createConnection(amqlib, host)
.mergeMap(createChannel)
然而,虽然这限制了我在测试方面的选择,因为我无法注入amqplib的模拟。我也许可以将它作为依赖项添加到我的函数参数中,但这样我就必须在树中遍历并传递依赖关系,如果任何其他组合将要使用它。此外,我希望能够模拟createConnection
和createChannel
函数,甚至不必测试我之前测试的相同行为,我是说我还必须将它们作为依赖项添加?
如果是这样的话,我可以在构造函数中有一个带有依赖项的工厂函数/类,然后使用某种形式的控制反转来管理它们并在必要时注入它们,但这实际上让我回到了我开始的地方,这是面向对象的方法
我知道我可能做错了什么,但老实说我找到了零(null,nada)关于测试具有函数组合的函数javascript的教程(除非这不是一个,在这种情况下是什么)。< / p>
答案 0 :(得分:4)
RxJS in Action的第9章免费提供here,如果您想要更深入的阅读(完全披露:我是作者之一),请完全涵盖该主题。
&安培; tldr;函数式编程鼓励透明的参数传递。因此,当您朝着使应用程序更具组合性迈出了一大步时,您可以更进一步,确保将副作用推向应用程序的外部。
这在实践中如何看待? Javascript中的一个有趣模式是函数currying,它允许您创建映射到其他函数的函数。因此,对于您的示例,我们可以将amqlib注入转换为参数:
import { createConnection, createChannel } from './';
export default (amqlib) => ({ host }) =>
createConnection(amqlib, host)
.mergeMap(createChannel);
现在你会像这样使用它:
import builder from './amqlib-adapter'
import amqlib from 'amqlib'
// Now you can pass around channelFactory and use it as you normally would
// you replace amqlib with a mock dependency when you test it.
const channelFactory = builder(amqlib)
您可以更进一步,并注入其他依赖项createConnection
和createChannel
。虽然如果你能够使它们成为纯函数,那么根据定义,由它们组成的任何东西也都是纯粹的函数。
如果我给你两个功能:
const add => (a, b) => a + b;
const mul => (a, b) => a * b;
或概括为curried函数:
const curriedAdd => (a) => (b) => a + b;
const curriedMul => (a) => (b) => a * b;
add
和multi
都被认为是纯函数,也就是说,给定相同的输入集会产生相同的输出(读取:没有边 - 效果)。你也会听到这被称为参考透明度(值得一个谷歌)。
鉴于上述两个函数是 pure ,我们可以进一步断言这些函数的任何组合也是纯粹的,即
const addThenMul = (a, b, c) => mul(add(a, b), c);
const addThenSquare = (a, b) => { const c = add(a, b); return mul(c, c); }
即使没有正式的证据,这至少应该是直观明确的,只要没有子组件添加副作用,那么整个组件就不应该有副作用。
由于它与您的问题有关,createConnection
和createChannel
是纯粹的,因此实际上并不需要模仿它们,因为它们的行为是在功能上驱动的(而不是内部状态驱动的) 。您可以单独测试它们以验证它们是否按预期工作,但由于它们是纯净的,它们的组成(即createConnection(amqlib, host).mergeMap(createChannel)
)也将保持纯净。
此属性也存在于Observables中。也就是说,两个纯Observable的组合将永远是另一个纯Observable。
答案 1 :(得分:0)
您是否考虑过调查任何这些嘲弄套餐?特别是rewire
可能是合适的。