(No "official" sources here, I'm afraid - it's not like there's a specification for how to test well. Just my opinions, which will hopefully be useful.)
When these static methods represent genuine dependencies, create wrappers. So for things like:
- ImageIO
- HTTP clients (or anything else network-related)
- The file system
- Getting the current time (my favourite example of where dependency injection helps)
... it makes sense to create an interface.
But many of the methods in Apache Commons probably shouldn't be mocked/faked. For example, take a method to join together a list of strings, adding a comma between them. There's no point in mocking these - just let the static call do its normal work. You don't want or need to replace the normal behaviour; you're not dealing with an external resource or something that's hard to work with, it's just data. The result is predictable and you'd never want it to be anything other than what it'll give you anyway.
I suspect that having removed all the static calls which really are convenience methods with predictable, "pure" outcomes (like base64 or URL encoding) rather than entry points into a whole big mess of logical dependencies (like HTTP) you'll find it's entirely practical to do the right thing with the genuine dependencies.
I'm all for fighting fire with fire, but turning bad code (lots of static methods) into other bad code (lots of singletons) doesn't sound like such a good idea to spend time on really.
From what you have written, this is all in a legacy code context, but you do have change access to the source where those static methods reside (maybe all, but the rest is too large to touch upon). So let's take a look at where you want to go ultimately:
- Why do you want to do that work at all? because the current code is untestable and makes new code hard to test as well
- Why is it untestable? because of all the static methods
- Why do we have those static methods? because they are used everywhere, have recursive calls (rely on each other), and hence are hard to get rid off
- Why do they all rely on each other? because utility classes have a tendency to do a lot of things instead of a single responsibility
My next question and suggestion to you would then be: Why don't you extract one responsibility after the other?
You may have a tough time identifying a responsibility, but you're probably having a concrete use case already that made you aware of this problem in the first place, so that should be a good start.
Once you have a separate, single responsibility and testable class available, you can still use it within the old class to replace parts of that class. Maybe you still have some duplication, because you needed that other static method's code in your new class, but it's still called from some other? Then it's a pointer to your next work area - identify what the common thing is that the two places share and extract it out from both.
Due to the sheer size you portray, this is going to be a slow and long running process, so every small responsibility that's out of that class has to be considered a win. Let the old static method rely on the new class, deprecate it, eradicate it whenever you see a call site. These things take time until you get to a point, where you can finally make that last investment and hole your team up for a day or two to get rid of all the remaining old and problematic code. As long as you take care, that no one uses these old methods for any new or modified code, you're getting closer to that goal. I wish you good luck for the journey.
Addendum - based on the additional information that these utility classes differ between different branches and different teams work with each of these: In short, you are trying to shoot a running target. Given the size of your codebase, the task itself is hard enough for a stable target, but I cannot recommend you continue down that path, until you first stopped that movement. Talk with the other teams and get them on board. If they object in any way shape or form, an already hard task becomes pretty much impossible. Hopefully, they all share your pain (or at least not caring about that part of the codebase) and you can get back to a unified state of these classes by merging them all.
Yes, I do suggest to merge all these branches w.r.t. the utility classes. I know it is its own hell. You have but three choices: make it a common problem (and share the solution work!), completely sever yourself, or just let it be as is.
The severing means that your team on your branch flat out defines itself as a different product line. You stop merging with the other guys and roll your own instead. It may not work, and even if it does, I have seen this come back to hit everyone in the company a few years down the road.
Anyways, every problem that cannot be solved locally within your own team('s codebase) is unfortunately immediately a matter of company politics. It's simple to pick a fight with your teammates to get rid of a nasty class in your team's code, but it's worth a second thought on whether you really want to go to war involving the other teams/stakeholders/managers/etc.
Best Answer
Generally speaking, @KonradMorawski's answer is good and complete. However, I would like to add that there is one scenario that I would consider it valuable to mock
static
methods - thestatic
factory method.Personally, in my code I very infrequently use
static
factory methods; I only use them if the created object is stateless, the factory is stateless, and the created object has no dependencies - and even then I'm still not very likely most of the time. Otherwise, I use an instance of a factory object, which can then be mocked.The issue with using a
static
factory method as provided is that the created objects will not bemock
s orspy
s, which means you will not be able to callverify
on them, and you will not control access to these objects in advance. For example consider a math application like the following:Note that you can't call
verify
on the returned complex numbers, nor can youverify
that the factory method itself was called the correct number of times. With PowerMock you can do both. Note that using PowerMock here is superior to just callingnew ComplexNumber()
.Of course the best way to do it is just inject an instance of a factory, which can then be mocked, avoiding PowerMock and allowing you to do all the normal testing behavior.