Free-standing functions and testing
Free-standing functions can be great. The easiest way of using them also makes the client code less convenient to test.
For example, say you're writing a React web app for photo sharing and you need to make requests to your application's backend. You could have some functions to do this: getUsers()
, getPhotosForUser()
, et cetera. The most easy and natural way to use these functions is to import them into the relevant component and use them directly.
This works pretty well until you need to unit test that component. At that point you would like to be able to replace the API call functions with stubs so you can test the component itself, isolated from the API calls which take a lot more setup and may be slow.
You can still unit test this code, with a few changes. You can change the constructor to accept all of the necessary functions as optional arguments (as describe here). This works fine. It comes with some inconveniences. You have to pass in each one individually. If there are lots this may be a problem, but with just two or three it's fine. It becomes hard to check tests statically or at a glance to see if you are using the real, API-hitting functions. But it's simple enough. If you only need a handful of functions, and not every client needs the same set, it might be the best solution.
One alternative is to bundle the functions together somehow. This makes a certain kind of static checking easier. In OO languages, that usually means interfaces or protocols plus classes. In theory you could inject modules in Python, treating them as objects that implement a protocol (duck typing). In something like OCaml you might use modules or functors. In Haskell you might just define partially applied functions for the "fully injected" version of the function and have tests pass in everything necessary. If your functions are bundled somehow – you don't have to pay close attention to what's being hardcoded at the function level, only at the class/module/etc. level. At this higher level it might be an easier problem, because there are (in theory) fewer classes/modules than functions.