Dependency injection was developed for languages that could not easily do that (like Java). It is just a design pattern that isnt that useful in python or js. You can decide to follow it if you think it will make your code cleaner/more readable.
It's interesting that you bring up Java, which compiles things into class files. You can technically "mix and match" things there to achieve mocking semantics by carefully constructing jars. The point of DI is to bring composability into the realm of the language. In JS, sure you can use complex machinery to get jest to mock out imports during a hidden compilation phase, but there's no contractual relationship between the mock and the API consumption deep in your unit. E.g. I could "mock" moment.js, but if the implementation switches to date-fns, then the mock is still a valid thing, but it's no longer useful/applicable. Whereas, if there is a contract that can be enforced via types/API/DI, then switching date util implementations will trigger compile errors.
Another reason passing dependencies as a parameter is great is to enforce constraints for a functional call. Say you have a database service that pools connections to your database. If you want to call some general function
loadPermissionRecords(services, id)
and be sure it only uses one connection for this invocation, you can wrap the database service with a facade that implements your resource constraint when calling it, eg loadPermissionRecords({ …services, database: database.withConnectionLimit(1) }, id)
Sure, you could add the connection limit as a parameter to loadPermissionRecords, but you’ll also have to pass it down to every other function in the call graph beneath loadPermissionRecords…Is it simpler/cleaner than DI? Arguable
Facades are good if you're not sure if the API/semantics are set in stone.
For moment.js, one typically only cares to mock specific calls related to things like current time, so it often makes more sense to refactor towards turning currentness into an explicit input, either by mocking the hard-coded moment.js call inside a convoluted unit, or more ideally, by making the value an input argument by design in the first place.
The latter is harder to get to if you already have the code written, so mocking moment.js calls could be an acceptable alternative.
You don't need to bring the whole OOP factory manager baggage if you just want simple contracts.
Patching feels a bit messy, and if you patch out some imported module "mymod" in the implementation, it breaks when "import mymod" is changed to "from mymod import myfun", which should be an implementation detail.
You could do something tricky with the import system to substitute modules, I suppose? It seems like this should solve some of the same use-cases as DI, but I haven't seen a clear and straight-forward description of how to go about it in a clean way.
1. The mechanism used (clearing or overriding the cache) isn’t available for ESM, where mocking is significantly more challenging.
2. That mechanism is also responsible for a lot of memory leaks when any modules are stateful.
2. That doesn’t mean they’re “right” or necessarily “better”.
3. It depends a lot on the complexity of the code under test. If it’s really simple code—and especially if the stuff you intend to mock performs well and doesn’t mutate—making it easier to mock is probably going the wrong direction.
4. For most (>50%) things, yes.
5. But also for most things (off the cuff guess: >75%), you might be better off not mocking anyway.
6. If you (like me) prefer unit tests, you’ll probably get even better results by isolating as much of your code as possible from anything you’d want to mock in the first place.
I've seen a number of occasions where a test using a mock passes one day then fails the next day because it turned out the mock was setup incorrectly, and the test was actually relying on wall clock, and it wasn't at all obvious that there was an issue because you could not statically analyze whether the mock was applied correctly.