javaspring-bootintegration-testinglistenerspring-boot-test

SpringBootTest - how to assert if context loading fails


I wrote an ApplicationListener that should check if the environment is prepared during context initialization. I'm having trouble testing the scenario since I'm adding the listener manually both in my configure() and main() methods.

ApplicationListener class:

public class EnvironmentPrepared implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

        @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
            //code that checks if conditions are met

            if (checkTrue) {
            throw new RuntimeException();
        }
    }
}

Main class:

    public class MyApp extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        setRegisterErrorPageFilter(false);
        return application.listeners(new EnvironmentPrepared()).sources(MyApp.class);
    }

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(MyApp.class);
        springApplication.addListeners(new EnvironmentPrepared());
        springApplication.run(args);
    }
}

The test I want to execute:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(loader = OverriddenProfilesTest.CustomLoader.class)
public class OverriddenProfilesTest {

    public static class CustomLoader extends SpringBootContextLoader {

        @Override
        protected SpringApplication getSpringApplication() {
            SpringApplication app = super.getSpringApplication();
            app.addListeners(new EnvironmentPrepared());
            return app;
        }
    }

    /**
     * Checks if spring can bootstrap everything 
     */
    @Test(expected = RuntimeException.class)
    public void test() {

    }
}

This would be the test I want. A RuntimeException is thrown but the exception happens during context initialization so the test doesn't even start.


Solution

  • Here is the solution I used. I removed the manual adding of the listener to the application and used spring.factories file instead.

    Regarding the test, I first created a custom runner class:

        public class SpringRunnerWithExpectedExceptionRule extends SpringJUnit4ClassRunner {
    
    public SpringRunnerWithExpectedExceptionRule(Class<?> clazz) throws InitializationError {
        super(clazz);
    }
    
    @Override
    protected Statement methodBlock(FrameworkMethod frameworkMethod) {
        List<ExpectedException> testRules = getTestClass().getAnnotatedFieldValues(null, ExpectedExceptionClassRule.class, ExpectedException.class);
        Statement result = super.methodBlock(frameworkMethod);
        for (TestRule item : testRules) {
            result = item.apply(result, getDescription());
        }
        return result;
    }}
    

    Then I create following annotation:

    @Retention(RUNTIME)
    @Target({ FIELD })
    public @interface ExpectedExceptionClassRule {
    
    }
    

    And finally, I was able to run the test with my runner:

    @RunWith(SpringRunnerWithExpectedExceptionRule.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class OverriddenProfilesTest {
    
        @ExpectedExceptionClassRule
        public static ExpectedException expectedException = ExpectedException.none();
    
        @BeforeClass
        public static void before() {
            expectedException.expectCause(runtimeExceptionMethod());
        }
    
    
        @Test
        public void testThatShouldThrowExceptionWhileSettingContext {
        }
    
        static Matcher<Throwable> runtimeExceptionMethod() {
            return new IsRuntimeException();
        }
    
        static class IsRuntimeException extends TypeSafeMatcher<Throwable> {
        //do stuff
        }
    

    More on the solution can be found here.