user1619397
user1619397

Reputation: 730

How to override/replace @MockBean field declared in the parent class with @Autowired real bean

To avoid the Spring context reloading again and again, I have moved @MockBean annotated injection to a parent class, something like this.

@SpringBootTest
.......
public abstract  BaseTest {
    @MockBean
    protected OneService oneService;

This serves the test classes which need a mock for OneService. However, for the test that I would like also extended from BaseTest, but inject the real OneService via @Autowired will not work, as it will inherit the mock injection from the BaseTest.

public AnotherTest extends BaseTest {
    @Autowired
    protected OneService oneService;

Even I used @Autowired annotation, the oneService field will be the mock instance inherited from BaseTest.

Is there a way I can force inject to use autowired?

Upvotes: 10

Views: 4010

Answers (1)

Sam Brannen
Sam Brannen

Reputation: 31197

In Java there is technically no way to override a field. When you declare a field with the same name in a subclass, that field shadows the field with the same name in the superclass. So that is one hurdle.

The second hurdle stems from the fact that there is no way in Spring Boot's testing support to turn off mocking of a bean in a subclass if the superclass configures the mocking via @MockBean.

Thus, the solution is to invert what you are trying to do by having the real bean injected via @Autowired in the superclass and then turn on mocking for the specific bean type in the subclass.

  1. Declare the @Autowired field in the superclass but do not redeclare the field in the subclass.
  2. Declare @MockBean at the class level in the subclass, specifying which classes (i.e., bean types) to be mocked.

That last step results in the inherited field being injected with a mock in the subclass.

The following two classes demonstrate this technique in practice.

@SpringBootTest(classes = BaseTests.Config.class)
class BaseTests {

    @Autowired
    protected Service service;

    @Test
    void service() {
        assertThat(service.getMessage()).isEqualTo("real");
    }

    @Configuration
    static class Config {

        @Bean
        Service service() {
            return new Service();
        }
    }

    static class Service {
        String getMessage() {
            return "real";
        }
    }

}
@MockBean(classes = BaseTests.Service.class)
class MockBeanTests extends BaseTests {

    @BeforeEach
    void setUpMock() {
        when(service.getMessage()).thenReturn("mock");
    }

    @Test
    @Override
    void service() {
        assertThat(service.getMessage()).isEqualTo("mock");
    }

}

Upvotes: 8

Related Questions