Dependency Injection and Taunt in Functional Javascript and RxJS

I am trying to rewrite a library written in classic OO Javascript into a more functional and reactive approach using RxJS and function composition. I started with the following two easily verifiable functions (I skipped the import of Observables):

Create-connection.js

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

Create-channel.js

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

All I have to do to test them is to enter the amqplib or connection layout and make sure that the correct methods are called, for example:

 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'); }); }); 

So now I would like to combine the two functions as follows:

 import amqplib from 'amqplib'; import { createConnection, createChannel } from './'; export default ({ host }) => createConnection(amqlib, host) .mergeMap(createChannel) 

However, this limits my options when it comes to testing, because I cannot enter the amqplib layout. I could add it to the function arguments as a dependency, but in this way I would have to go all the way in the tree and pass the dependencies if any other composition would use it. Also, I would like to be able to mock the createConnection and createChannel functions createConnection even experiencing the same actions that I tested before, could I say that I would also have to add them depending on them?

If so, I could use the factory function / class with the dependencies in my constructor, and then use some form of management access to control them and if necessary introduce them, however this essentially brings me back to where I started, that there is Object Oriented approach.

I understand that I'm probably doing something wrong, but to be honest, I found zero (zero, nada) tutorials on testing functional javascript with a composition of functions (unless it is one, and in this case there is one) .

+8
javascript dependency-injection unit-testing functional-programming rxjs
source share
2 answers

Chapter 9 of RxJS in action is available for free here and covers the topic in some detail if you want a deeper reading (full disclosure: I am one of the authors).

? TL; DR; Functional programming facilitates the transparent passing of arguments. Therefore, while you have taken a good step towards making your application more complex, you can go even further by making sure that your side effects are crowding out your application.

What does it look like in practice? Well, one funny template in Javascript is the currying function, which allows you to create functions that map to other functions. Thus, for your example, we could convert the amqlib injection to an argument instead:

 import { createConnection, createChannel } from './'; export default (amqlib) => ({ host }) => createConnection(amqlib, host) .mergeMap(createChannel); 

Now you will use it like this:

 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) 

You can take another step, as well as introduce other createConnection and createChannel . Although, if you could make them pure functions, then by definition, everything consisting of them would also be a pure function.

What does it mean?

If I give you two functions:

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

Or generalized as curried functions:

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

Both add and multi are considered pure functions, that is, with the same set of inputs the result will be the same (read: there are no side effects). You will also hear that this is called referential transparency (it costs google).

Given that the two functions above are pure, we can further assert that any composition of these functions will also be pure, i.e.

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

Even without formal proof, this should be at least intuitive, if none of the subcomponents add side effects, then the component as a whole should not have side effects.

Since this relates to your problem, createConnection and createChannel are clean, there is really no need to simulate them, as their behavior is functionally controlled (as opposed to internal state). You can test them yourself to make sure they are working properly, but since they are clean, their composition (i.e. createConnection(amqlib, host).mergeMap(createChannel) ) will also stay clean.

Bonus

This property also exists in Observables. That is, the composition of two pure Observers will always be another pure Observer.

+4
source share

Have you considered viewing any of these mocking packages? In particular, rewire is suitable.

Proxyquire, rewire, SandboxedModule and Sinon: pros and cons

0
source share

All Articles