Spring-boot: Allow for registering mocks with ApplicationContextRunner

Created on 6 Jul 2018  ·  10Comments  ·  Source: spring-projects/spring-boot

It would be nice if one could register mock beans with the ApplicationContextRunner in some easy way.
@MockBeans in the test does not seem to be picked up.
Sometimes the auto-configuration interacts with other beans, and being able to easily register them for each test would be beneficial.

declined

Most helpful comment

Perhaps something like this might be useful?

new ApplicationContextRunner()
  .withMockBeans(SomeBean.class)
  .withUserConfiguration(MyConfiguration.class)

All 10 comments

Neither @MockBean nor @SpyBean will work as they're intended for use with @SpringBootTest. ApplicationContextRunner is intended for use in a "plain" test using a standard runner.

Have you considered using a normal @Configuration class that provides the necessary mock beans? That's what we do in our own tests such as this one and we haven't felt a need for anything more.

Perhaps something like this might be useful?

new ApplicationContextRunner()
  .withMockBeans(SomeBean.class)
  .withUserConfiguration(MyConfiguration.class)

That would help for basic scenarios where you do not need to configure any behaviour on the mock, but I don't think it would help much when you do. You also lose the ability to specify things like the name of the bean or its order. I guess withMockBeans could take something richer that captures all of that, but it feels like quite a lot of work for not much benefit.

withMockBeans looks just like what I need. The behaviour will be given per test anyway ( in the run() method - at the start) - but the bean needs to be there. I went the way via a separate @ConfigurationClass given via withUserConfig - but it's tedious and verbose.

If setting up the behaviour once the application context has been refreshed is sufficient, then I would argue that your test shouldn't be using ApplicationContextRunner. Rather than writing an integration test that requires an application context, I'd recommend a unit test that tests the component that uses the mocks directly.

Where we've used mocks in combination with ApplicationContextRunner, the mocked beans have been used during application context refresh. As such the behaviour can't be easily tested without an application context so an integration test is appropriate.

@davidkarlsen If using mocks with a plain unit test isn't an option, can you please describe your needs in a bit more detail? I'd like to understand why that's the case before we add something that encourages people to write integration tests when a simpler and faster unit test would have sufficed.

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Thanks. In a comment above you said “The behaviour will be given per test anyway ( in the run())”. I can’t see any configuration of the mock’s behaviour in the example above. Can you please clarify?

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

ah - that's in another test, here you go:

 @Configuration
    @Import(AbstractDataSourceConfiguration.class)
    public static class TestConfig {

        @Bean
        public JdbcTemplate getJdbcTemplate( DataSource dataSource ) {
            return new JdbcTemplate( dataSource );
        }

        // https://github.com/spring-projects/spring-boot/issues/13718
        @Bean
        public OracleEndToEndMetricDataSourceWrapper.ContextResolver contextResolver() {
            OracleEndToEndMetricDataSourceWrapper.ContextResolver contextResolver = Mockito.mock(OracleEndToEndMetricDataSourceWrapper.ContextResolver.class);

            return contextResolver;
        }
    }

  @Test
    public void testDbOperation() {
        contextRunner.withUserConfiguration(TestConfig.class).run( c -> {
            JdbcTemplate jdbcTemplate = c.getBean(JdbcTemplate.class);
            Assertions.assertThat(jdbcTemplate.getDataSource()).isInstanceOf(OracleEndToEndMetricDataSourceWrapper.class);
            OracleEndToEndMetricDataSourceWrapper.ContextResolver contextResolver = c.getBean(OracleEndToEndMetricDataSourceWrapper.ContextResolver.class);

                Mockito.when(contextResolver.resolve()).thenReturn( new OracleEndToEndMetricDataSourceWrapper.Context( "someModule",
                    "someAction",
                    "someClientid",
                    "someExecutionContextId",
                    Short.valueOf( "1" ),
                    RandomStringUtils.randomAscii( 30 ) ) );

            jdbcTemplate.queryForObject( "select 1 from dual", Integer.class );
            Mockito.verify(contextResolver).resolve();

        });
    }

I guess not all tests will be fortunate enough to use mocks like that. In many causes I imagine the initialization will need to be done as part of the @Bean method.

Given the limited usefulness of withMockBeans alone, and the added complexity that would be required if we were to offer a way to setup the mock, I think we should not rush to add this. Using the @Bean method to create the mock is a little inconvenient, but not too bad.

Was this page helpful?
0 / 5 - 0 ratings