I am having a hard time trying to unit test F# code with external dependencies.
In C# (my background) you would typically have a class with a dependency passed in, which is then re-used. Apologies for my sample code, it's dumb but I'm just trying to illustrate my point.
public class Foo {
IDependency d;
public Foo(IDependency d) { this.d = d; }
public int DoStuff(string bar) { return d.DoSomethingToStuff(bar); }
public int DoMoreStuff(string bar) {
int i = d.DoSomethingToStuff(bar);
return d.DoSomethingElseToStuff(bar, i);
}
}
I'm trying to be pragmatic with F# and avoid using classes and interfaces (unless I need to interop with other .NET languages).
So my approach in this scenario is to have module and some functions with the dependencies passed in as functions. I found this tecnique here
module Foo
let doStuff bar somethingFunc =
somethingFunc bar
let doMoreStuff bar somethingFunc somethingElseFunc =
let i = somethingFunc bar
somethingElseFunc bar i
The two problems I have with this code is:
I need to keep passing my dependencies around. In C#, it's passed in the constructor and re-used. You can imagine how quickly this gets out of control if somethingFunc
is used in several places.
How do I unit test that dependencies have been executed? Again in C# I'd use a mocking framework and assert that certain methods were called.
How do I approach these problems in the F# world?
It's not too difficult mapping SOLID concepts like Dependency Injection to Functional-style F# - one of the keys is to realize that there's a strong relationship between objects and closures.
In the present case, it would help to reorder the function arguments so that the 'dependencies' go first:
module Foo =
let doStuff somethingFunc bar =
somethingFunc bar
let doMoreStuff somethingFunc somethingElseFunc bar =
let i = somethingFunc bar
somethingElseFunc bar i
This will enable you to compose functions using partial function application:
let doStuff' = Foo.doStuff somethingImp
Now, doStuff'
is a closure, because it closes over the concrete function somethingImp
. Essentially, it captures the dependency, so it works just like an object with an injected dependency, and you can still invoke it with the remaining bar
argument:
let bar = 42
let actual = doStuff' bar
Testing
Here's an example of using local functions as stubs:
module Tests =
let ``Data flows correctly through doMoreStuff`` () =
let somethingFunc bar =
assert (bar = 42)
1337
let somethingElseFunc bar i =
assert (bar = 42)
assert (i = 1337)
"Success"
let actual = Foo.doMoreStuff somethingFunc somethingElseFunc 42
assert (actual = "Success")
Here, for the sake of simplicity, I've used the assert keyword, but for proper tests, you should define a proper assertion function, or use your favourite assertion library.
Normally, I would tend to loosen the verification of input arguments, as it may make the Test Doubles too tightly coupled to a particular implementation. Also, keep in mind that you should use Stubs for Queries, and Mocks for Commands - in this example, there are only Queries, so all the Test Doubles are Stubs: although they do verify input if they are invoked, the test doesn't verify that they are invoked at all.
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