Spring Framework
Unit Testing
Bean Overriding
Dependency Injection
Java Testing

Overriding an Autowired Bean in Unit Tests

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When people ask how to override an autowired bean in a unit test, there are usually two different scenarios hiding underneath. Either the test is a true unit test and Spring should not be involved at all, or it is a Spring-backed test where the application context is loaded and one bean needs to be replaced.

The right answer depends on that distinction. For pure unit tests, prefer constructor injection and pass a fake or mock directly. For Spring tests, replace the bean with @MockBean, a test configuration, or a @Primary test bean.

Prefer Plain Unit Tests When You Can

If the class under test has only a few dependencies, the cleanest option is to avoid the Spring container entirely. Constructor injection makes that easy:

java
1public interface EmailSender {
2    void send(String to, String body);
3}
4
5public class NotificationService {
6    private final EmailSender emailSender;
7
8    public NotificationService(EmailSender emailSender) {
9        this.emailSender = emailSender;
10    }
11
12    public void notifyUser(String email) {
13        emailSender.send(email, "Welcome");
14    }
15}

Then the test can provide a mock directly:

java
1import org.junit.jupiter.api.Test;
2import org.mockito.Mockito;
3
4class NotificationServiceTest {
5    @Test
6    void sendsWelcomeEmail() {
7        EmailSender sender = Mockito.mock(EmailSender.class);
8        NotificationService service = new NotificationService(sender);
9
10        service.notifyUser("[email protected]");
11
12        Mockito.verify(sender).send("[email protected]", "Welcome");
13    }
14}

This is faster than loading Spring and is usually what "unit test" should mean.

Use @MockBean in Spring Test Contexts

If you really need Spring to create the object graph, @MockBean is usually the simplest override mechanism in Spring Boot tests:

java
1import org.junit.jupiter.api.Test;
2import org.springframework.beans.factory.annotation.Autowired;
3import org.springframework.boot.test.context.SpringBootTest;
4import org.springframework.boot.test.mock.mockito.MockBean;
5
6import static org.mockito.Mockito.verify;
7
8@SpringBootTest
9class NotificationServiceSpringTest {
10
11    @Autowired
12    private NotificationService notificationService;
13
14    @MockBean
15    private EmailSender emailSender;
16
17    @Test
18    void overridesBeanWithMock() {
19        notificationService.notifyUser("[email protected]");
20        verify(emailSender).send("[email protected]", "Welcome");
21    }
22}

@MockBean replaces the real bean in the application context with a Mockito mock. That makes it ideal for integration-style tests that still need Spring wiring.

Override with a Test Configuration

Sometimes a mock is not enough. You may need a lightweight fake implementation with predictable behavior. In that case, define a test configuration:

java
1import org.springframework.boot.test.context.TestConfiguration;
2import org.springframework.context.annotation.Bean;
3import org.springframework.context.annotation.Primary;
4
5@TestConfiguration
6class TestBeans {
7    @Bean
8    @Primary
9    EmailSender testEmailSender() {
10        return (to, body) -> System.out.println("Fake send to " + to);
11    }
12}

Then import it into the test:

java
1import org.springframework.boot.test.context.SpringBootTest;
2import org.springframework.context.annotation.Import;
3
4@SpringBootTest
5@Import(TestBeans.class)
6class NotificationServiceFakeBeanTest {
7}

The @Primary annotation ensures Spring prefers the test bean when both real and test versions exist.

Why Field Injection Makes Testing Harder

Bean overriding questions often come from code that uses field injection:

java
1@Service
2public class NotificationService {
3    @Autowired
4    private EmailSender emailSender;
5}

That style works, but it pushes you toward container-based tests. Constructor injection makes dependencies explicit and keeps both production code and tests easier to reason about.

Choose the Smallest Test Style That Fits

A useful rule is:

  • pure unit test: instantiate the class manually
  • Spring slice or integration test: use @MockBean
  • custom fake dependency in Spring context: use test configuration plus @Primary

The more Spring you involve, the slower and heavier the test becomes. Use that weight only when the test genuinely needs container behavior.

Common Pitfalls

  • Calling something a unit test while loading the full Spring application context.
  • Using field injection, which makes manual construction harder.
  • Overriding beans with test configuration but forgetting @Primary.
  • Mocking too much in a Spring test and turning it into a slow unit test.
  • Reaching for Spring-based override mechanisms when constructor injection would avoid the problem entirely.

Summary

  • In true unit tests, do not autowire; pass mocks or fakes through the constructor.
  • In Spring Boot tests, @MockBean is the simplest way to replace an autowired dependency.
  • Use @TestConfiguration and @Primary when you need a fake implementation instead of a mock.
  • Prefer constructor injection because it makes dependencies explicit and tests easier to write.
  • Choose the lightest test style that still verifies the behavior you actually care about.

Course illustration
Course illustration

All Rights Reserved.