Idiomatic approach for mocking in tests

I need to write tests for some code which switches behaviour based on values of several env vars.

I’m coming from Python where the use of mock.patch in tests is pretty ubiquitous and it would be trivial to patch os.getenv in the test cases to return various useful values.

I found Kaputt which has a Mock.from_function method but AFAICT this only generates a mock and has no way to patch the pre-existing implementation.

I guess this is not possible in OCaml?

So my question is: what would be an idiomatic way to handle this?

About the only way I can think of is to restructure my code into a dependency-injection style using a functor or first-class module generation, so that I can pass in a substitute implementation of Sys.getenv in the tests.

F# for fun an profit has a good series of posts on how to deal with dependencies. And F# is close enough to OCaml that most should be understandable and more or less directly transferable.

1 Like

Thanks, that article has a good summary of options.

After posting I was thinking about it over lunch and the “Dependency Rejection”/“Dependency Parameterization” approach occurred to me, basically remove the various calls to Sys.getenv from the “inside” of the code, and instead pass around a Map of env values from the “outside”.

The problem with that in my case is a) the stdlib doesn’t seem to have a way to get a mapping of all env vars and b) didn’t really want to add extra envmap arg to a whole bunch of functions

I ended up going the functor route and it’s working fine, seemed the minimally invasive option.

It’s slightly annoying having to restructure the actual code to add a layer of indirection that is only needed by tests, but I guess there is no way around it in the more static languages.

Why do you need to get a Map of all env vars? You can parameterize by a just a function string -> string. (Sys.getenv by default, a closure fun var -> StringMap.find var test_map in tests)

With effects (soon™) you alternatively could add an effect Getenv : string -> string and define let getenv var = perform (Getenv var) and provide a default handler (BTW what’s the status of default handlers in 5.0?) that just calls Sys.getenv and override the handler for tests.

1 Like

Ok yes that’s another option, instead of pass around the map of all env vars, pass around a getter function