I have a function responsible for collecting a bunch of configurations and making a bigger configuration out of all these parts. So it's basically:
let applyUpdate updateData currentState =
if not (someConditionAbout updateData) then
log (SomeError)
let this = getThis updateData currentState.Thingy
let that = getThat updateData currentState.Thingy
let andThat = createThatThing that this updateData
// blablablablablabla
{ currentState with
This = this
That = that
AndThat = andThat
// etc. }
I currently have unit tests for getThis
, getThat
, createThatThing
, but not for applyUpdate
. I don't want to re-test what getThis
and etc. are doing, I just want to test the logic specific to applyUpdate
and just stub getThis
. In an object-oriented style, these would be passed via an interface through dependency injection. In a functional style I'm unsure as to how to proceed:
// This is the function called by tests
let applyUpdateTestable getThisFn getThatFn createThatThingfn etc updateData currentState =
if not (someConditionAbout updateData) then
log (SomeError)
let this = getThisFn updateData currentState.Thingy
// etc
{ currentState with
This = this
// etc. }
// This is the function that is actually called by client code
let applyUpdate = applyUpdateTestable getThis getThat etc
This seems the functional equivalent of Bastard Injection, but beyond that I'm mainly concerned with:
How do deal with these problems in functional programming?
You said:
In an object-oriented style, these would be passed via an interface through dependency injection.
And the same approach is used in FP, but rather than injecting via the object constructor, you "inject" as parameters to the function.
So you are on the right track with your applyUpdateTestable
, except that this would be also used as real code, not just as testable code.
For example, here's the function with the three extra dependencies passed in:
module Core =
let applyUpdate getThisFn getThatFn createThatThingfn updateData currentState =
if not (someConditionAbout updateData) then
log (SomeError)
let this = getThisFn updateData currentState.Thingy
// etc
{ currentState with
This = this
// etc. }
Then, in the "production" code, you inject the real dependencies:
module Production =
let applyUpdate updateData currentState =
Core.applyUpdate Real.getThis Real.getThat Real.createThatThingfn updateData currentState
or more simply, using partial application:
module Production =
let applyUpdate =
Core.applyUpdate Real.getThis Real.getThat Real.createThatThing
and in the test version, you inject the mocks or stubs instead:
module Test =
let applyUpdate =
Core.applyUpdate Mock.getThis Mock.getThat Mock.createThatThing
In the "production" example above, I statically hard-coded the dependencies on the Real
functions, but alternatively,
just as with OO style dependency injection, the production applyUpdate
could be created by some top-level coordinator
and then passed into the functions that need it.
This answers your questions, I hope:
There are more complex versions of this approach, such as the "Reader" monad, but the above code is the simplest approach to start with.
Mark Seemann has a number of good posts on this topic, such as Integration Testing and SOLID: the next step is Functional and Ports and Adapters.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With