test a Spring Boot WebClient outside of a HttpServletRequest context
Asked Answered
J

1

1

My Spring boot 2.5 application is a fairly standard web application that exposes some endpoints and calls other services. In the usual use cases, an external request comes in, and applies some business logic, which is going to call other services, using WebClient configured with Oauth2.

However, my application also contains a scheduled task that needs to make some calls to other services.

That's where the problems are starting : I believe I face the same issue as in Spring Security 5 Calling OAuth2 Secured API in Application Runner results in IllegalArgumentException , ie servletRequest cannot be null when using webClient from a non Servlet environment.

I thought I would be able to reproduce this easily through an integration test, but I am not. I already have a SpringBootTest for the scheduled task, in which I load the Spring context, get access to the class that has the logic, and simply call the method that is scheduled when deployed in prod... but it's not failing : I am not able to reproduce.

When I debug the test, I see the servletRequest is indeed not null when I run the test (while it is when it runs in prod) : there's a Spring MockhttpServletrequest, so I don't have the same problem as in prod.

enter image description here

I have tried to run the test with no "web" support at all, with

@SpringBootTest(webEnvironment = WebEnvironment.NONE) 

But when I do that, some of the required elements are missing for my application to start : I have 2 webClients configured in my application, the "normal" one, and the one I plan to use for the scheduler :

  @Bean
  @Primary
  WebClient servletWebClient(ClientRegistrationRepository clientRegistrations,
                             OAuth2AuthorizedClientRepository authorizedClients) {

    //this constructor  will configure internally a RemoveAuthorizedClientOAuth2AuthorizationFailureHandler,
    // and onAuthorizationFailure will be called on it when we get a 401
    var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);

    oauth.setDefaultClientRegistrationId("keycloak");


    return WebClient.builder()
        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .apply(oauth.oauth2Configuration())
        .build();
  }

  @Bean
  @Qualifier("forScheduler")
  WebClient schedulerWebClient(OAuth2AuthorizedClientManager authorizedClientManager) {

    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth =new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);

    oauth.setDefaultClientRegistrationId("keycloak");

    return WebClient.builder()
        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .apply(oauth.oauth2Configuration())
        .build();
  }

  @Bean
  public OAuth2AuthorizedClientManager authorizedClientManagerForScheduler(
      ClientRegistrationRepository clientRegistrationRepository,
      OAuth2AuthorizedClientService clientService)
  {

    OAuth2AuthorizedClientProvider authorizedClientProvider =
        OAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials()
            .build();

    AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
        new AuthorizedClientServiceOAuth2AuthorizedClientManager(
            clientRegistrationRepository, clientService);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    return authorizedClientManager;
  }

But when configuring my test with webEnvironment = WebEnvironment.NONE , then no ClientRegistrationRepository gets loaded, and the declared servletWebClient can't be instantiated, as it's one of its dependencies.

Testing this manually is quite difficult/expensive, so I would really like to get some kind of validations through my integration tests that my change with 2 webClients will work before I deploy it.

How can I test that scenario correctly ? or in other words, is there a way to have a default Spring Boot test, but without a mockServletContext being initialized by Spring ?

Juneberry answered 8/11, 2021 at 14:58 Comment(1)
This is exactly the issue that I have. I did not configure the AuthorizedClientServiceOAuth2AuthorizedClientManager, so it used Default one, but tests did not fail so I noticed this issue on main runtime. #78826292Superdreadnought
J
1

I found the solution with a different approach, with a custom TestExecutionListener.

more details in https://mcmap.net/q/2037311/-spring-genericwebapplicationcontext-gets-loaded-despite-web-application-type-none

Juneberry answered 16/11, 2021 at 20:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.