Configure @MockBean component before application start

Marcel picture Marcel · Nov 12, 2016 · Viewed 12.3k times · Source

I have a Spring Boot 1.4.2 application. Some code which is used during startup looks like this:

@Component 
class SystemTypeDetector{
    public enum SystemType{ TYPE_A, TYPE_B, TYPE_C }
    public SystemType getSystemType(){ return ... }
}

@Component 
public class SomeOtherComponent{
    @Autowired 
    private SystemTypeDetector systemTypeDetector;
    @PostConstruct 
    public void startup(){
        switch(systemTypeDetector.getSystemType()){   // <-- NPE here in test
        case TYPE_A: ...
        case TYPE_B: ...
        case TYPE_C: ...
        }
    }
}

There is a component which determines the system type. This component is used during startup from other components. In production everything works fine.

Now I want to add some integration tests using Spring 1.4's @MockBean.

The test looks like this:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyWebApplication.class, webEnvironment = RANDOM_PORT)
public class IntegrationTestNrOne {
    @MockBean 
    private SystemTypeDetector systemTypeDetectorMock;

    @Before 
    public void initMock(){
       Mockito.when(systemTypeDetectorMock.getSystemType()).thenReturn(TYPE_C);
    }

    @Test 
    public void testNrOne(){
      // ...
    }
}

Basically the mocking works fine. My systemTypeDetectorMock is used and if I call getSystemType -> TYPE_C is returned.

The problem is that the application doesn't start. Currently springs working order seems to be:

  1. create all Mocks (without configuration all methods return null)
  2. start application
  3. call @Before-methods (where the mocks would be configured)
  4. start test

My problem is that the application starts with an uninitialized mock. So the call to getSystemType() returns null.

My question is: How can I configure the mocks before application startup?

Edit: If somebody has the same problem, one workaround is to use @MockBean(answer = CALLS_REAL_METHODS). This calls the real component and in my case the system starts up. After startup I can change the mock behavior.

Answer

Maciej Walkowiak picture Maciej Walkowiak · Oct 23, 2019

In this case you need to configure mocks in a way we used to do it before @MockBean was introduced - by specifying manually a @Primary bean that will replace the original one in the context.

@SpringBootTest
class DemoApplicationTests {

    @TestConfiguration
    public static class TestConfig {

        @Bean
        @Primary
        public SystemTypeDetector mockSystemTypeDetector() {
            SystemTypeDetector std = mock(SystemTypeDetector.class);
            when(std.getSystemType()).thenReturn(TYPE_C);
            return std;
        }

    }

    @Autowired
    private SystemTypeDetector systemTypeDetector;

    @Test
    void contextLoads() {
        assertThat(systemTypeDetector.getSystemType()).isEqualTo(TYPE_C);
    }
}

Since @TestConfiguration class is a static inner class it will be picked automatically only by this test. Complete mock behaviour that you would put into @Before has to be moved to method that initialises a bean.