Some teams forbid Mockito's @InjectMocks and require the object under test to be constructed by hand in every test. What is the argument on each side, and how would you decide the rule for a large codebase?
answer
- Where should a wiring mistake be caught: compile, test, or prod?
- Fails open — null, no error
- Compile break on constructor change = feature, not friction
- Wide constructor pain is design feedback
- Segmented policy: new code explicit, legacy tolerated
basics
~20 s@InjectMocks saves boilerplate on wide constructors but resolves wiring reflectively and fails open, so constructor changes rot tests silently. Manual construction costs one line and turns those changes into compile errors. Most large codebases end up preferring explicit construction, with the annotation tolerated for wide legacy constructors.
solid answer
~60 s**For the annotation:** less boilerplate, especially on classes with many dependencies; adding a collaborator does not force edits across dozens of test classes; the pattern is uniform and recognisable. **Against it:** injection is reflective and *fails open* — an unmatched dependency becomes `null` with no error, surfacing later as an NPE inside production code. Same-type dependencies are resolved by a name heuristic. Which strategy runs depends on the class's shape, so setter-only dependencies are silently skipped when a wide constructor exists. And the very thing sold as a benefit — tests still compile after a constructor change — is what lets them rot. **Deciding:** ask what you want a signature change to do. If the answer is "break loudly, immediately, at compile time", require manual construction; it costs one line in a `@BeforeEach`. If the constructor is wide and stable and edits would touch fifty test classes, the annotation is a reasonable local trade. My default: explicit construction as the standard, `@InjectMocks` allowed for legacy classes, and "the constructor is too wide to type" treated as feedback about the class, not the test.
go deeper
Know that both approaches exist and that manual construction makes constructor changes fail at compile time.
Contrast the two concretely: boilerplate saved versus silent nulls and reflective, shape-dependent wiring.
Recommend a default with reasons, and name the mitigations — test data builders, a null-dependency assertion in the base test class.
Decide as policy: segment new versus legacy code, tie the choice to where you want failures detected, note the design-pressure effect, and write the convention down so it stops being relitigated.
## Framing the decision This is not a question about which API is nicer. It is a question about **where you want a wiring mistake to be detected**: at compile time, at test time, or in production. `@InjectMocks` and manual construction sit at different points on that axis, and a large codebase should choose deliberately rather than by habit. ## The case for @InjectMocks **Boilerplate reduction at scale.** A class with six dependencies produces a six-argument `new` in every test class that touches it. If eight test classes exist, adding a seventh dependency is eight edits. With `@InjectMocks` plus a new `@Mock` field, it is often zero edits in classes that do not care about the new collaborator. **Uniformity.** Everyone recognises the shape: mocks above, object under test below. New joiners copy it without thinking, and reviewers do not have to read wiring code. **It tolerates awkward hierarchies.** Deep inheritance, package-private constructors, setter-injected legacy classes — the annotation copes with all of them without the test having to know which route the dependency takes. ## The case against **It fails open.** Mockito explicitly does not report an injection failure. An unresolved constructor argument is passed as `null`; a dependency that is not reachable by the winning strategy stays `null`. The symptom is an NPE thrown from production code in whichever test happens to exercise that path — and if no test exercises it, no symptom at all, which is worse. **Compilation stops protecting you.** With manual construction, adding a constructor parameter breaks every test that builds the object. That is not friction; that is the compiler telling you which tests now need a decision about the new collaborator. `@InjectMocks` removes that signal, and the exact same property is what its advocates list as a benefit. Whether it is a benefit depends on whether you consider "tests keep compiling" a good outcome when the object's contract changed. **Behaviour depends on class shape.** Which of constructor/setter/field injection runs is decided at runtime by the class's structure, and the chain stops at the first success. A reader of the test cannot tell what was injected without reading the production class. Same-type dependencies fall back to a field-name heuristic; constructor arguments are matched by type alone. **It hides design pressure.** A constructor with nine parameters is painful to write in a test — and that pain is information. Automating it away removes the main feedback loop that keeps classes from accumulating dependencies. ## How I would decide **Start from the failure mode you can afford.** In a codebase where tests are the main safety net and constructors change often, silent nulls are expensive: the test suite goes green while coverage of the new dependency is zero. That argues for manual construction as the default. **Measure the actual cost.** "Too much boilerplate" is usually one `@BeforeEach` with one `new`. If the objection is really about a fifteen-argument constructor, the honest fix is to split the class, or introduce a small test data builder/factory shared across test classes — which gives you the boilerplate reduction *and* compile-time checking. **Segment the codebase.** A blanket ban is rarely worth the migration cost. A workable policy: - New code: construct explicitly. Enforce in review, not with a custom lint rule you will have to maintain. - Legacy classes with wide or setter-based wiring: `@InjectMocks` allowed, no rewrite required. - Any class where two dependencies share a type: explicit construction, no exceptions — the name heuristic is not something to bet on. **Add the cheap safety net either way.** If the annotation stays, a shared assertion in the base test class (or a small helper that reflects over the object under test and fails on any null dependency) restores the loud failure at near-zero cost. Most teams never bother, which is exactly why the null shows up in production paths instead. **Do not let this become a religious rule.** The cost difference per test is small; the cost difference at the moment a constructor changes is not. Argue the policy in those terms, decide once, write it down in the testing conventions, and stop relitigating it in code review. ## What a strong answer sounds like A principal-level answer does not simply pick a side. It names the tradeoff (silent runtime null vs. one line of typing), identifies the second-order effects (loss of compile-time feedback, suppressed design pressure, hidden strategy selection), proposes a segmented policy rather than a blanket ban, and mentions a mitigation for whichever side the team lands on.
- If a team keeps @InjectMocks, what cheap guardrail restores the loud failure?A shared helper in the base test class that reflects over the object under test after injection and fails if any dependency field is null. It costs a few lines once and converts the silent null into an immediate, well-named setup failure. It does not fix same-type mis-assignment, but it catches the dominant case.
- Does a test data builder or factory solve the boilerplate argument?Largely, yes. A small factory that assembles the object under test from mocks gives one place to change when the constructor grows, keeps every call site compiling against the real signature, and reads better than a long new expression. It is the option most teams overlook when framing the choice as annotation-versus-new.
- When is @InjectMocks clearly the right call?On legacy classes whose dependencies arrive through setters or field injection and which you cannot restructure, and on wide but stable constructors where migrating dozens of test classes buys little. In both cases the annotation is a pragmatic accommodation of existing design, not an endorsement.
saying these in an interview costs you the question
- Arguing purely on typing effort without mentioning the silent-null failure mode
- Presenting "tests still compile after a constructor change" as an unqualified benefit
- Proposing a blanket ban with no migration path for legacy code
- Assuming strict stubbing or some Mockito setting will catch injection failures
- Ignoring that a painful constructor in a test is feedback about the class