Mutation not killed when it should be with a method with an auto-injected field
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
If a mutation survives in code that uses an auto-injected field, the usual reason is not that mutation testing is broken. It is that the tests do not make the dependency's effect observable enough to fail when the mutant changes behavior.
Why Auto-Injected Fields Cause Confusion
Field injection hides part of the object's state outside the constructor. That makes tests less explicit and can produce situations where:
- the dependency is a mock with overly broad stubbing
- the method under test does not assert on the dependency's effect
- the mutated line is effectively irrelevant to the final assertion
- the mutant is equivalent for the exercised scenario
A surviving mutant is a signal that the test did not prove the behavior strongly enough.
Example Of The Problem
Consider a Spring-style service with field injection:
Suppose a mutation changes basePrice - discount to basePrice + discount, or removes the repository call entirely. If the test only checks that the result is non-null or greater than zero, the mutant may survive.
That test is too weak. Both the real implementation and several mutants can still satisfy it.
Make The Behavior Observable
A mutation gets killed when the test makes the changed behavior visible. For the example above, assert the exact result:
Now a mutant that changes subtraction to addition is much more likely to die.
Prefer Constructor Injection
Field injection also makes tests awkward because you need Spring context bootstrapping or reflection-based field setting. Constructor injection is better for mutation testing and unit testing in general:
Test:
This version makes the dependency explicit and eliminates hidden wiring from the test.
Surviving Mutants Are Not Always Test Bugs
Sometimes the mutant is equivalent, meaning it changes the code syntactically but not behaviorally for that path. That can happen with:
- dead code
- redundant null checks
- logging-only branches
- framework-generated wiring that tests do not care about
But do not assume equivalence too quickly. Most of the time, a surviving mutant around an injected field means the test is not asserting the right outcome or interaction.
Verify Interactions When They Matter
If the important behavior is that a collaborator must be called, assert that too:
Interaction assertions should not replace result assertions, but they can kill mutants that bypass collaborator calls.
Reduce Framework Noise In Unit Tests
Mutation testing works best when unit tests exercise plain objects with explicit dependencies. If the test spins up a Spring container for every case, it becomes slower and the actual behavioral contract can be harder to see.
A good rule is:
- use constructor injection in the production class
- test the class without Spring when possible
- assert exact outputs or exact side effects
- use interaction verification only for meaningful collaborator behavior
That makes surviving mutants easier to interpret.
Common Pitfalls
- Using field injection and then patching tests with reflection instead of improving the design.
- Writing assertions that are too broad, such as non-null or positive checks.
- Mocking collaborators in a way that hides whether the mutated code path was really used.
- Assuming every surviving mutant is equivalent without proving it.
- Testing through the full framework when a focused unit test would expose the behavior more clearly.
Summary
- A surviving mutant around an auto-injected field usually means the dependency's effect is not asserted strongly enough.
- Constructor injection makes mutation testing and ordinary unit testing much clearer.
- Assert exact outputs and important collaborator interactions.
- Treat equivalent mutants as the exception, not the default explanation.
- If a mutant survives, ask what observable behavior should have changed and why the test did not catch it.

