8

I am writing an integration test for Spring Boot application. All goes well as long as I'm using 100% runtime configuration for the test. But when I'm trying to provide just one custom bean for the bean, everything breaks.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CombinedControllerIntegrationTest2 {

@TestConfiguration
static class ContextConfiguration {

    @Bean
    @Primary
    public SolrDocumentTypeMapRepository solrDocumentTypeMapRepository() {
        LOG.debug("SolrDocumentTypeMapRepository is being initialized.");

// etc.

code variant above is causing the real runtime SolrDocumentTypeMapRepository to load. Therefore the ContextConfiguration in my test class is ignored.

If I try to use @Configuration instead of @TestConfiguration on my inner ContextConfiguration, execution falls to another extreme - it ends with:

org.springframework.context.ApplicationContextException: 
Unable to start EmbeddedWebApplicationContext due to missing
EmbeddedServletContainerFactory bean.

Because, apparently, the rest of configuration is not loaded.

If I try to put

@ContextConfiguration(
 classes = { CombinedControllerIntegrationTest2.ContextConfiguration.class,
             GatewayApplication.class})

on my main testing class, it fails the same way as in #1 - i.e. my ContextConfiguration is ignored.

Any ideas?

P.S. I know I can use @MockBean (and this even works in other cases), but here, because at some point I have dependency on @PostConsruct methods in the main code, @MockBeans are useless.

Manuel Jordan
  • 15,253
  • 21
  • 95
  • 158
Alexander
  • 753
  • 2
  • 10
  • 25
  • I faced similar issue as posted here https://stackoverflow.com/questions/45522778/springboottest-not-creating-inner-beans-while-loading-the-context – Barath Oct 02 '17 at 06:28

3 Answers3

13

Unit Test with One Bean

Just use @RunWith(SpringRunner.class) annotation, it should work. You can also use @RunWith(SpringJUnit4ClassRunner.class). Both should work.

Please don't use @SpringBootTest annotation. It will wire up the whole application.

Here is your updated example,

@RunWith(SpringRunner.class)
public class CombinedControllerIntegrationTest2 {

    @TestConfiguration
    static class ContextConfiguration {

        @Bean
        public SolrDocumentTypeMapRepository solrDocumentTypeMapRepository() {
            LOG.debug("SolrDocumentTypeMapRepository is being initialized.");
            return new SolrDocumentTypeMapRepository(...);
        }
    }

    @Autowired
    private SolrDocumentTypeMapRepository repository;

    @Test
    public void test() {
        assertNotNull(repository);
    }
}

Integration Test with a Replaced Bean

  • Create a new test Spring Boot application. It should exclude the configuration class (for e.g., SolrConfiguration) which is responsible for creating SolrDocumentTypeMapRepository bean.

    @SpringBootApplication
    @ComponentScan(basePackages = {
            "com.abc.pkg1",
            "com.abc.pk2"},
            excludeFilters = {
                    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, 
                    value = SolrConfiguration.class)})
    public class TestApplication {
        public static void main(String[] args) throws Exception {
            SpringApplication.run(TestApplication.class, args);
        }
    }
    
  • Now, use the @ContextConfiguration annotation in your test class to add the TestApplication.class and the ContextConfiguration.class. This will wire up your application with the all the required beans including the replaced bean. Shown below is the updated test class,

    @ActiveProfiles("test")
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(webEnvironment = 
        SpringBootTest.WebEnvironment.RANDOM_PORT)
    @ContextConfiguration(classes = {TestApplication.class, 
        CombinedControllerIntegrationTest2.ContextConfiguration.class})
    public class CombinedControllerIntegrationTest2 {
    
        @TestConfiguration
        static class ContextConfiguration {
    
            @Bean
            public SolrDocumentTypeMapRepository solrDocumentTypeMapRepository() {
                LOG.debug("SolrDocumentTypeMapRepository is being initialized.");
                return new SolrDocumentTypeMapRepository(...);
            }
        }
    
        ...
    }
    
Indra Basak
  • 7,124
  • 1
  • 26
  • 45
  • Thanks, this is pretty much what I'm doing for _unit_ tests. Not using @SpringBootTest annotation here would mean that I'm not doing integration test. My application has dozens of other beans that I do want to use. – Alexander Oct 02 '17 at 16:08
  • @Alexander Updated my posting to show you can use the replaced bean along with rest of your application beans in your integration test. – Indra Basak Oct 02 '17 at 17:55
  • Thank you! "TestApplication" approach worked. I still kind of disappointed in "@TestConfiguration" annotation. Spring documentation leads to believe that it enables partial configuration overriding, but it doesn't. – Alexander Oct 02 '17 at 22:16
  • Yes, Spring document doesn't cover or clear about corner use cases. – Indra Basak Oct 02 '17 at 23:08
2

Use @Mockean to mock your repository. And add behavior in the test:

@SpringBootTest
@AutoConfigureMockMvc
@RunWith(SpringRunner.class)      
public abstract class IntegrationTest {

  @MockBean
  SolrDocumentTypeMapRepository solrDocumentTypeMapRepository;

  @Test
  public void mySuperTest(){ 
Mockito.when(solrDocumentTypeMapRepository.getById(Mockito.any())).thenReturn(someInstance);
Assert.assertEquals(solrDocumentTypeMapRepository.getById("someId"), someInstance);
}
}
Valeriy K.
  • 2,616
  • 1
  • 30
  • 53
-2

Instead of using @Runwith(SpringJunit4Classrunner.class) use @RunWith(SpringRunner.class). That should help you with the error.

chetank
  • 392
  • 3
  • 17
  • Nope, this does not change anything. – Alexander Oct 02 '17 at 02:49
  • https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html – chetank Oct 02 '17 at 02:51
  • [According to the documentation](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.html), there is no difference: _Simply annotate a JUnit 4 based test class with `@RunWith(SpringJUnit4ClassRunner.class)` or `@RunWith(SpringRunner.class)`_ – Ilya Serbis Oct 01 '20 at 21:48