Dependency injection in practice is almost always implemented for tests.
In terms of architecture, it's usually a case of YAGNI. Every dependency injected is a configuration point that is almost never altered, except for mocks in testing. When this style infects a whole codebase, it makes it far harder to read and navigate, because code flow is dependent on runtime data flow. Heavy use of indirections that only ever go to the one place is a bad code smell, and it's a stench over the entire field of enterprise Java.
Code that is easy to read is easy to refactor. Except for natural architectural chokepoints that are typically intrinsic to the problem being solved, you're fooling yourself if you don't think architecture changes are needed for most refactoring - dependency injection isn't buying you what you think you're buying. Every injection point you create is a prediction about the future, about the possibilities of change, but there's one gotcha: the future is hard to predict, so most of your decisions are wrong.
You're better off being agile, following YAGNI, doing the simplest thing that will work, and altering it when requirements change.
I've done DI many times for things other than tests - swapping out different algorithms is the most common case (BetaBanditCalculator -> TimeVaryingBayesianBanditCalculator -> HierarchicalPersonalCharacteristicCalculator, etc). It also makes testing easy, which is great, but that's far from the only use case.
DI tends to work great for the integration point between systems - e.g., connecting the REST or Thrift interface to the calculation backend and datastore.
The fact that a concept is hard to use in Enterprise Java is a poor argument against anything other than Enterprise Java.
DI is nothing else than partially applied functions, which is useful to limit the number of values flowing explicitly to the call point. Limiting the data flow diameter is good, as our brains can handle so much information simultaneously.
On the other hand, DI frameworks are a "modern" way to do global objects. I have no clue what problem they solve or why using a DI framework is a good idea. I've had the "pleasure" to work with a Java DI framework project last week, it turned 10 lines of code into 300 lines of boilerplate spread around 3 different packages.
Furthermore, I agree, the pervasive use of mocks is a smell. The point of tests is to have test suite T.A to validate module A. Littering the test base with mocks of A which don't pass the test suite T.A is just a recipe for pain, both semantically and as increasing the amount of code change required by a refactoring. A is changing? Go chase the 100 mocks that make their own assumptions about A and fix them. Your tests are now part of the liability under change instead of being the safety net that tells you whether your module B depending on A is still working.
One extra point: what @barrkel is saying here works much better in a language which is fairly concise. In more verbose languages, any kind of change becomes more difficult (there is simply more code to change and more opportunities to make mistakes), so it is understandable that people want to build in "hooks" for extension.
In terms of architecture, it's usually a case of YAGNI. Every dependency injected is a configuration point that is almost never altered, except for mocks in testing. When this style infects a whole codebase, it makes it far harder to read and navigate, because code flow is dependent on runtime data flow. Heavy use of indirections that only ever go to the one place is a bad code smell, and it's a stench over the entire field of enterprise Java.
Code that is easy to read is easy to refactor. Except for natural architectural chokepoints that are typically intrinsic to the problem being solved, you're fooling yourself if you don't think architecture changes are needed for most refactoring - dependency injection isn't buying you what you think you're buying. Every injection point you create is a prediction about the future, about the possibilities of change, but there's one gotcha: the future is hard to predict, so most of your decisions are wrong.
You're better off being agile, following YAGNI, doing the simplest thing that will work, and altering it when requirements change.