gpt4 book ai didi

javascript - 函数式 javascript 和 RxJS 中的依赖注入(inject)和模拟

转载 作者:数据小太阳 更新时间:2023-10-29 04:45:21 27 4
gpt4 key购买 nike

我正在尝试使用 RxJS 和函数组合将用经典 OO Javascript 编写的库重写为更具功能性和 react 性的方法。我从以下两个易于测试的函数开始(我跳过了 Observables 的导入):

创建连接.js

export default (amqplib, host) => Observable.fromPromise(amqplib.connect(host))

创建 channel .js

export default connection => Observable.fromPromise(connection.createChannel())

测试它们所需要做的就是注入(inject) 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)

然而,这限制了我在测试时的选择,因为我无法注入(inject) amqplib 的模拟。我也许可以将它作为依赖项添加到我的函数参数中,但那样的话,如果任何其他组合要使用它,我将不得不在树中一直向上遍历并传递依赖项。此外,我希望能够模拟 createConnectioncreateChannel 函数,甚至不必测试我之前测试过的相同行为,我的意思是我必须将它们添加为也有依赖关系?

如果是这样的话,我可以在我的构造函数中有一个带有依赖项的工厂函数/类,然后使用某种形式的控制反转来管理它们并在必要时注入(inject)它们,但这基本上让我回到了我开始的地方,这是面向对象的方法.

我知道我可能做错了什么,但老实说,我发现了零个(null,nada)关于使用函数组合测试功能性 javascript 的教程(除非这不是一个,在这种情况下是什么)。

最佳答案

RxJS in Action 第 9 章免费提供 here如果您想更深入地阅读,并且非常全面地涵盖了该主题(完全披露:我是作者之一)。

&tldr;函数式编程鼓励透明的参数传递。因此,虽然您在使应用程序更具可组合性方面迈出了良好的一步,但您可以通过确保将副作用推到应用程序外部来走得更远。

这在实践中看起来如何?好吧,Javascript 中一个有趣的模式是函数柯里化(Currying),它允许您创建映射到其他函数的函数。因此,对于您的示例,我们可以将 amqlib 注入(inject)转换为参数:

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)

您可以更进一步,还可以注入(inject)其他依赖项 createConnectioncreateChannel .尽管如果你能够使它们成为纯函数,那么根据定义,由它们组成的任何东西也将是一个纯函数。

这是什么意思?

如果我给你两个函数:

const add => (a, b) => a + b;
const mul => (a, b) => a * b;

或概括为柯里化(Currying)函数:

const curriedAdd => (a) => (b) => a + b;
const curriedMul => (a) => (b) => a * b;

两者都是 addmulti被认为是函数,也就是说,给定相同的一组输入将导致相同的输出(阅读:没有副作用)。您还会听到这被称为引用透明度(值得谷歌)。

鉴于上述两个函数是,我们可以进一步断言这些函数的任何组合也将是纯函数,即

const addThenMul = (a, b, c) => mul(add(a, b), c);
const addThenSquare = (a, b) => { const c = add(a, b); return mul(c, c); }

即使没有正式的证明,这至少在直觉上应该是清楚的,只要没有任何子组件增加副作用,那么作为一个整体的组件就不应该有副作用。

因为它与您的问题有关,createConnectioncreateChannel是纯粹的,那么实际上不需要模拟它们,因为它们的行为是功能驱动的(而不是内部状态驱动)。您可以独立测试它们以验证它们是否按预期工作,但由于它们是纯净的,它们的组成(即 createConnection(amqlib, host).mergeMap(createChannel) )也将保持纯净。

奖金

这个属性也存在于 Observables 中。也就是说,两个纯 Observable 的组合将始终是另一个纯 Observable。

关于javascript - 函数式 javascript 和 RxJS 中的依赖注入(inject)和模拟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45636541/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com