Mastering @Autowired and @InjectMocks in Spring Boot Tests: Avoid These Pitfalls
Learn the hidden traps of @Autowired and @InjectMocks in Spring Boot 3.2 tests.
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
- ✓Java 17+ (Spring Boot 3.x requires it)
- ✓Spring Boot 3.2 project with spring-boot-starter-test
- ✓Basic understanding of JUnit 5 and Mockito
- ✓Familiarity with dependency injection concepts
• @Autowired injects real Spring beans into your test, but can cause slow startup and hidden dependencies • @InjectMocks creates a mock-friendly instance of the class under test, but can silently swallow null fields • Mixing them in integration tests leads to unpredictable behavior and timeouts • Use @WebMvcTest for controllers, @DataJpaTest for repositories, and plain @InjectMocks for unit tests • Always verify that @InjectMocks doesn't leave null dependencies — use @Mock for all collaborators
Think of @Autowired like ordering a full course meal at a restaurant — you get everything the chef prepares, even what you didn't ask for. @InjectMocks is like assembling your own sandwich with only the ingredients you choose, but if you forget the bread, your sandwich falls apart. In testing, @Autowired loads the whole Spring kitchen, while @InjectMocks lets you control each slice.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
You've been there: a Spring Boot test that passes locally but fails on CI. Or an integration test that takes 45 seconds to start for a single endpoint. The culprit? Misunderstanding @Autowired and @InjectMocks. In Spring Boot 3.2 with JUnit 5.10 and Mockito 5.7, these annotations are the backbone of test configuration, yet they're often used interchangeably with disastrous results. I've debugged production incidents where a misused @Autowired caused a test to hit a real database, corrupting test data. And I've seen @InjectMocks silently create objects with null fields, leading to NullPointerExceptions that only surface in edge cases. This article will give you the battle-tested patterns I've used across payment-processing systems and SaaS billing platforms. You'll learn when to use each annotation, how to avoid the common traps, and what to do when your test suddenly starts failing after a Spring Boot upgrade. By the end, you'll write tests that are fast, reliable, and actually test what you think they're testing.
Understanding @Autowired in Spring Boot Tests
When you annotate a field with @Autowired in a test class, you're telling Spring to inject a fully configured bean from the application context. This is powerful for integration tests where you want to test the real wiring of your components. However, in Spring Boot 3.2, @Autowired brings the entire application context unless you use slice annotations like @WebMvcTest or @DataJpaTest. For a typical payment-processing service, this means loading all controllers, services, repositories, security filters, and even the embedded servlet container — even if you only need one service. The startup time can balloon to 30-45 seconds. Worse, if your application has @ConditionalOnProperty or @Profile annotations, the test might load beans you didn't expect. I've seen a test that used @Autowired on a repository and accidentally triggered a Flyway migration, corrupting the test database. The golden rule: use @Autowired only when you need the real Spring context. For unit tests, avoid it entirely and use @InjectMocks with @Mock.
What the Official Docs Won't Tell You
The official Spring Boot documentation tells you that @Autowired injects beans from the context, and @InjectMocks creates a mock-friendly instance. What they don't tell you is that @InjectMocks has a dangerous silent behavior: if you don't provide a @Mock for every dependency, the field is left null. No warnings, no errors — just a NullPointerException at runtime. I've debugged a production incident where a test passed for months because the mocked service never hit the null path. Then a code change triggered the null field, and the test failed only in CI, not locally. The official docs also gloss over the interaction between these annotations in a Spring Boot test. If you use @InjectMocks inside a @SpringBootTest class, Mockito will try to inject mocks into the instance, but Spring's @Autowired will also try to inject real beans. This can lead to unpredictable behavior where some fields are real beans and others are mocks. The fix: never mix them in the same test class. Use @ExtendWith(MockitoExtension.class) for pure unit tests and @SpringBootTest for integration tests. Another undocumented trap: @InjectMocks doesn't work well with constructor injection that has multiple parameters of the same type. Mockito might inject the wrong mock, leading to subtle bugs.
When to Use @Autowired vs @InjectMocks: A Decision Framework
The choice between @Autowired and @InjectMocks depends on what you're testing. For unit tests that verify business logic in isolation, use @InjectMocks with @Mock for all collaborators. This gives you complete control over dependencies and lightning-fast execution. For integration tests that verify the Spring context wiring, use @Autowired with slice annotations. Here's my rule of thumb: if you're testing a service that calls a repository, use @InjectMocks and mock the repository. If you're testing that the repository actually persists data correctly, use @DataJpaTest with @Autowired for the repository. For controllers, use @WebMvcTest with @MockBean for services. In a payment-processing system, we had a PaymentService that called three external APIs. Using @InjectMocks with @Mock for each API gave us tests that ran in 50ms. The same test with @SpringBootTest took 40 seconds and was flaky due to network timeouts. The decision also affects test maintenance: @Autowired tests break when you add new beans to the context, while @InjectMocks tests break when you change constructor signatures. Choose based on which failure mode is less painful for your team.
Common Pitfall: Mixing @Autowired and @InjectMocks in the Same Test
I've seen this in codebases across multiple companies: a test class that uses both @SpringBootTest and @ExtendWith(MockitoExtension.class), with fields annotated with both @Autowired and @InjectMocks. This creates a Frankenstein test where Spring injects real beans into some fields, and Mockito creates mocks for others. The result is unpredictable behavior that changes based on the order of field declaration. In Spring Boot 3.2, the test context is shared across tests, so if one test modifies a real bean's state, it affects other tests. This is especially dangerous in payment-processing systems where state matters. The fix is simple: never combine these annotations in the same test class. If you need a real Spring context with some mocks, use @MockBean or @SpyBean instead of @InjectMocks. These annotations register the mock in the Spring context, so all beans get the mock. For example, in a billing service test, you might want a real repository but a mocked email service. Use @SpringBootTest with @MockBean for the email service and @Autowired for the repository.
Advanced: Constructor Injection with @InjectMocks
In Spring Boot 3.2, constructor injection is the recommended approach for beans. However, @InjectMocks has specific behavior with constructors. If your class has a single constructor, Mockito will use it and inject mocks for each parameter. If you have multiple constructors, Mockito chooses the one with the most parameters. This can lead to surprises if you add a new constructor. For example, in a real-time analytics service, we added a second constructor for backward compatibility. Suddenly, @InjectMocks started using the wrong constructor, injecting mocks into parameters that didn't exist before. The fix: always have a single constructor in your production code, or use @InjectMocks with explicit constructor parameters via the @InjectMocks annotation's constructorParameters attribute (available in Mockito 5.x). Another advanced technique: use Mockito.mock() and Mockito.spy() directly in your test setup for fine-grained control. This gives you the ability to pass specific mock instances to the constructor, avoiding any ambiguity. For complex classes with 5+ dependencies, I prefer this approach over @InjectMocks.
Debugging Flaky Tests Caused by @Autowired and @InjectMocks
Flaky tests are the bane of every developer's existence. In Spring Boot tests, flakiness often stems from shared state between tests due to @Autowired. When you use @SpringBootTest, the application context is cached and reused across tests. If one test modifies a bean's state (e.g., a static counter or a mutable field), it affects other tests. I've debugged a case where a test for a payment gateway service passed in isolation but failed when run with other tests because a mock was accidentally storing state in a static map. Another common flakiness source: @InjectMocks with field injection can leave null fields if the test doesn't mock all dependencies. This causes NullPointerExceptions that only appear when a specific code path is executed. The debug approach: enable Mockito's strict stubbing with @ExtendWith(MockitoExtension.class) and set only when necessary. For Spring tests, use lenient()@DirtiesContext to force a fresh context for tests that modify state. Also, use @TestMethodOrder(MethodOrderer.OrderAnnotation.class) to control test execution order during debugging.
Best Practices for @Autowired and @InjectMocks in Spring Boot 3.2
After years of debugging production incidents and reviewing hundreds of pull requests, here are my battle-tested best practices. First, prefer constructor injection in production code. This makes your tests cleaner because you can create the object with explicit mocks. Second, in tests, use @ExtendWith(MockitoExtension.class) for unit tests and never mix it with @SpringBootTest. Third, for integration tests, use slice annotations (@WebMvcTest, @DataJpaTest, @JsonTest) instead of @SpringBootTest whenever possible. Fourth, when you must use @SpringBootTest, use @MockBean for external dependencies and @Autowired for the class under test. Fifth, always verify that your test doesn't leave null fields. Use a custom JUnit extension or a simple assertion in @BeforeEach that checks for null dependencies. Sixth, use Mockito's verifyNoMoreInteractions() to ensure you're not missing any important interactions. In a payment-processing system, this caught a bug where a service was supposed to log transactions but the mock wasn't being called. Finally, keep your tests fast. A test suite that takes 10 seconds is acceptable; 10 minutes is not. If you're using @SpringBootTest, consider breaking it into smaller, focused tests.
Conclusion: Test with Confidence
Mastering @Autowired and @InjectMocks is essential for writing reliable Spring Boot tests. The key takeaways: use @Autowired with slice annotations for integration tests, use @InjectMocks with @Mock for unit tests, and never mix them in the same class. Always verify that @InjectMocks doesn't leave null fields, and enable Mockito's strict stubbing to catch unused stubs. In production systems like payment processing and SaaS billing, these practices prevent flaky tests, reduce CI times, and catch bugs before they reach production. Remember: a test that passes randomly is worse than no test at all. It gives false confidence. The next time you see a test that takes 40 seconds to start or fails intermittently, you'll know exactly where to look. Use the debug guide below to diagnose common issues, and refer to the cheat sheet for quick fixes. Spring Boot 3.2 offers powerful testing tools, but with great power comes great responsibility. Use them wisely, and your tests will be fast, reliable, and maintainable.
The Case of the Corrupted Test Database
- Always verify which database your test is using — check the active profile and datasource configuration.
- Use @DataJpaTest for JPA repository tests — it auto-configures an embedded database and enforces rollback.
- Never assume @SpringBootTest uses an in-memory database; explicitly configure it or use Testcontainers.
- Add @Transactional to integration tests that modify data to ensure rollback between tests.
when() calls. Use lenient() only for intentionally unused stubs.mvn test -Dtest=YourTest -X | grep -i "null"Add @BeforeEach void checkNulls() { assertThat(field1).isNotNull(); }| File | Command / Code | Purpose |
|---|---|---|
| UserServiceIntegrationTest.java | @SpringBootTest | Understanding @Autowired in Spring Boot Tests |
| PaymentServiceTest.java | @ExtendWith(MockitoExtension.class) | What the Official Docs Won't Tell You |
| DecisionMatrixTest.java | @ExtendWith(MockitoExtension.class) | When to Use @Autowired vs @InjectMocks |
| BadTestMixingAnnotations.java | @SpringBootTest | Common Pitfall |
| AnalyticsServiceTest.java | public class AnalyticsService { | Advanced |
| FlakyTestDebugging.java | @ExtendWith(MockitoExtension.class) | Debugging Flaky Tests Caused by @Autowired and @InjectMocks |
| BestPracticesTest.java | @Service | Best Practices for @Autowired and @InjectMocks in Spring Boo |
| FinalExampleTest.java | @ExtendWith(MockitoExtension.class) | Conclusion |
Key takeaways
Interview Questions on This Topic
Explain the difference between @Autowired and @InjectMocks in Spring Boot tests.
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
That's Spring Boot. Mark it forged?
6 min read · try the examples if you haven't