spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.12k stars 37.96k forks source link

Provide hook for framework customizations of the WebTestClient MockServerSpec [SPR-15674] #20233

Closed spring-projects-issues closed 6 years ago

spring-projects-issues commented 7 years ago

Rossen Stoyanchev opened SPR-15674 and commented

WebTestClient should provide something along the lines of ConfigurableMockMvcBuilder#apply, i.e. a single place in the setup where a framework (such as Spring Security) can customize the MockServerSetup. So the end result might be:

WebTestClient.bindToController(new TestController())
    .apply(springSecurity())
    .build();

In addition to a hook for build-time MockServerSetup customizations, we should also explore a hook for per-instance customizations that would turn this:

ExchangeMutatorWebFilter mutator = new ExchangeMutatorWebFilter();

WebTestClient client = WebTestClient.bindToApplicationContext(this.context)
    .webFilter(mutator)
    .build();

client.filter(mutator.perClient(withUser()))
    .get().uri("/principal")
    .exchange()
    .expectStatus().isOk();

Into something more like:

WebTestClient client = WebTestClient.bindToApplicationContext(this.context)
    .apply(springSecurity())
    .build();

client.apply(springSecurityWithUser())
    .get().uri("/principal")
    .exchange()
    .expectStatus().isOk();

Affects: 5.0 RC2

Issue Links:

spring-projects-issues commented 7 years ago

Rossen Stoyanchev commented

This is now in. There is an example in ExchangeMutatorTests.

spring-projects-issues commented 7 years ago

Rob Winch commented

I think these changes now make io.projectreactor.ipc:reactor-netty required for WebTestClient.bindToController which should not be the case. I put together a sample project at https://github.com/spring-projects/spring-framework-issues/tree/master/SPR-15674 If you run the tests it fails with

java.lang.NoClassDefFoundError: reactor/ipc/netty/http/client/HttpClient
    at org.springframework.http.client.reactive.ReactorClientHttpConnector.<init>(ReactorClientHttpConnector.java:47)
    at org.springframework.web.reactive.function.client.DefaultWebClientBuilder.initExchangeFunction(DefaultWebClientBuilder.java:248)
    at org.springframework.web.reactive.function.client.DefaultWebClientBuilder.build(DefaultWebClientBuilder.java:195)
    at org.springframework.test.web.reactive.server.DefaultWebTestClientBuilder.build(DefaultWebTestClientBuilder.java:149)
    at org.springframework.test.web.reactive.server.AbstractMockServerSpec.build(AbstractMockServerSpec.java:74)
    at sample.WebClientTests.setup(WebClientTests.java:39)

Change gradle/dependency-management.gradle to point at spring-framework-bom:5.0.0.RC2 resolves the issue.

spring-projects-issues commented 7 years ago

Rossen Stoyanchev commented

It should be fixed now.

spring-projects-issues commented 7 years ago

Rob Winch commented

Thanks for the update. That did seem to resolve that issue. Another issue is that sessions don't appear to work when mutate is called on WebTestClient when WebTestClient.bindToController is invoked. I put together a sample at https://github.com/rwinch/spring-webclient-sample/tree/sessions

If you run the test it fails. However, if you revert the commit the test works against Spring Framework 5 RC2 (it also worked until today). Here is a summary of the test:


import org.junit.Before;
import org.junit.Test;
import org.springframework.http.ResponseCookie;
import org.springframework.test.web.reactive.server.ExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Mono;

import java.util.Optional;

import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;

public class WebClientTests {

    private WebTestClient client;

    @Before
    public void setup() {
        this.client = WebTestClient
                .bindToController(new SessionController())
                .build();
    }

    @Test
    public void sessionWorks() throws Exception {
        ExchangeResult result = this.client
                .mutate()
                .filter(basicAuthentication("foo","bar"))
                .build()
                .get()
                .uri("/session/set")
                .exchange()
                .returnResult(String.class);

        ResponseCookie session = result.getResponseCookies().getFirst("SESSION");

        this.client
                .get()
                .uri("/session/get")
                .cookie(session.getName(), session.getValue())
                .exchange()
                .expectStatus().isOk()
                .expectBody(String.class).isEqualTo("Hello");
    }

    @RestController
    @RequestMapping("/session")
    public class SessionController {
        private String attrName = "attrName";

        @GetMapping("/set")
        public Mono<String> set(WebSession session) {
            session.getAttributes().put(attrName, "Hello");
            return Mono.just("Set");
        }

        @GetMapping("/get")
        public Mono<String> get(WebSession session) {
            Optional<String> attribute = session.getAttribute(attrName);
            String value = attribute.orElse(null);
            return Mono.justOrEmpty(value);
        }
    }
}
spring-projects-issues commented 7 years ago

Rossen Stoyanchev commented

It looks like we were relying on default WebSessionManager initialization in WebHttpHandlerAdapter, and after the changes for mutate to be able to change the mock server filters, the session manager instance was getting re-created. Should be fixed now. I've added an integration test also matching to your example.

spring-projects-issues commented 7 years ago

Rob Winch commented

Thanks! I think everything is working again