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:
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.
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.
I was able to fix it like this
@RunWith(SpringRunner.class) @SpringBootTest(classes = MyWebApplication.class, webEnvironment = RANDOM_PORT) public class IntegrationTestNrOne { // this inner class must be static! @TestConfiguration public static class EarlyConfiguration { @MockBean private SystemTypeDetector systemTypeDetectorMock; @PostConstruct public void initMock(){ Mockito.when(systemTypeDetectorMock.getSystemType()).thenReturn(TYPE_C); } } // here we can inject the bean created by EarlyConfiguration @Autowired private SystemTypeDetector systemTypeDetectorMock; @Autowired private SomeOtherComponent someOtherComponent; @Test public void testNrOne(){ someOtherComponent.doStuff(); } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With