I’ve recently stumbled upon a useful OCaml design pattern for functorizing an existing module, without changing the way existing clients of that module use it. This is really useful for stubbing out dependencies when you’re refactoring code for unit tests. Here’s a simplified example from a bit of code that I refactored today.

The above code is loosely based on xen-api’s network daemon, a service which configures an XCP host’s networking. The above module is for the Open vSwitch backend, which uses the ovs-vsctl command line tool to convigure the vSwitch controller. I just implemented some new functionality in the make_bond_properties function (not shown above), and I want to test that ovs-vsctl command is being invoked properly.

I want to be able to test that the list of arguments we’re passing to the vsctl function is correct, but this function calls vsctl directly with the arguements, so I can’t “see” them in my test case. We could just split create_bond into create_bond_arguments and do_create_bond functions. But if we ever write unit tests for the other 20 functions that call vsctl, we’ll have to do the exact same thing for all of them. Instead, we’ll refactor the Ovs module so that we can pass in an alternative implementation of vsctl.

You can see that we’ve just inserted a few lines of code around the original Ovs module implementation. We moved the vsctl function into Ovs.Cli, and we moved the rest of the definition of Ovs into Ovs.Make(Cli : Cli_S). The neat bit is at the end, when we include Make(Cli) inside of module Ovs. This calls the Make functor with a module that contains the original definition of the vsctl function, and includes that in the Ovs definition. Now we have a functorized module, which we can customize in our test cases. Yet we haven’t changed the definition of the original Ovs module at all, so we don’t have to change any of the code that depends on the Ovs module! This trick also works on toplevel modules in *.ml files, too.

So the test is actually performed by the vsctl command, which we injected into the Ovs module using the Ovs.Make functor. Easy!

One more thought: I had considered using first class modules to inject test dependencies into a module like Ovs. This would have worked as well, but I couldn’t think of a good way to do it that wouldn’t have required rewriting all the Ovs call sites (I didn’t think to hard about it, so maybe it’s easier than I think). Also, first class modules are really meant for swapping dependencies at runtime, more akin to what Java programmers mean when they say “dependency injection.” In this case, we really would rather have the dependency injection done at compile time, so there’s no real benefit to using first class modules.