Search code examples
spring-bootspring-boot-test

Spring GenericWebApplicationContext gets loaded despite web-application-type: NONE


In My Spring 2.5 web application, I am trying to write a test in which I disable the fact that it's a web application : this is because the application also contains some scheduled task(s) that use some Oauth2 components to call other services.

These Oauth2 components behave differently depending on whether they are triggered from a Servlet context or not (see official doc).

Right now, when I execute my @SpringBootTest, I see that :

[org.springframework.context.support.AbstractApplicationContext] [main] [629] [DEBUG] Refreshing org.springframework.web.context.support.GenericWebApplicationContext        

I would like to disable this, so in my application.yml, I set spring.main.web-application-type=NONE , but it doesn't work I still see the WebApplicationContext (I also set the property to a wrong value, to make sure that was taken into account and failing : it was, so I know my applicaion.yml file is read and taken into account by me test).

My hope is that once I am able to disable this, then ServletTestExecutionListener will not be triggered and no MockServletContext will be created.


Solution

  • Because we can't easily enable the auto-configurations we need that are disabled when we set webEnvironment = SpringBootTest.WebEnvironment.NONE (see comments in https://stackoverflow.com/a/69970013/3067542 ), we have to take a different approach.

    We can't easily prevent ServletTestExecutionListener to be called, but we can add our own TestExecutionListener that will reset the parts we need in the test context. Luckily, the listener we add gets called after ServletTestExecutionListener so can indeed "undo" what we need.

    import org.springframework.test.context.TestContext;
    import org.springframework.test.context.support.AbstractTestExecutionListener;
    import org.springframework.web.context.request.RequestContextHolder;
    
    /**
     * It's difficult to test a scheduled task that is part of a web application : out of the box, Spring Boot will set up a mock servlet context
     * through {@link org.springframework.test.context.web.ServletTestExecutionListener}. But in production, the servlet context is null and HTTP calls through {@code WebClient} may fail
     * if it's not configured correctly - see <a href="https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/oauth2/client/AuthorizedClientServiceOAuth2AuthorizedClientManager.html">the doc</a>
     * for an example of how it should typically configured.
     * If not configured correctly, WebClient calls may fail at runtime, and we may not be able to reproduce easily in a test, because servletRequest is no tnull like in prod, but set to a mock servlet context.
     *
     * Using {@code ResettingServletContextToNullListener}, we reset to null the servletContext that was initialized in {@link org.springframework.test.context.web.ServletTestExecutionListener}, and create a context that is much closer to what it is at runtime in production
     */
    public class ResettingServletContextToNullListener extends AbstractTestExecutionListener {
    
      @Override
      public void beforeTestMethod(TestContext testContext) {
        System.out.println("resetting servletContext to null, like it is at runtime when a scheduled task is triggered..");
        RequestContextHolder.setRequestAttributes(null);
      }
    
    }
    

    and then we can simply add this in our test :

    @SpringBootTest
    @TestExecutionListeners(value = ResettingServletContextToNullListener.class, mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
    class MyScheduledTaskIntegrationTest {
    
    ...
    
    }
    

    With that setup, the test context for the scheduled task is similar to what it is when it runs in production, ie servletRequest is null : we can then adjust our Oauth2 config so that it doesn't fail when it's the case.