WebFlux test support for server endpoints

Issue: SPR-14590
This commit is contained in:
Rossen Stoyanchev
2017-02-08 16:48:46 -05:00
parent 1901cc65fd
commit 4b4201efa1
27 changed files with 3414 additions and 2 deletions

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.reactive.server.samples;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Tests with error status codes or error conditions.
*
* @author Rossen Stoyanchev
*/
@SuppressWarnings("unused")
public class ErrorTests {
private WebTestClient client;
@Before
public void setUp() throws Exception {
this.client = WebTestClient.bindToController(new TestController()).build();
}
@Test
public void notFound() throws Exception {
this.client.get().uri("/invalid")
.exchange()
.assertStatus().isNotFound();
}
@Test
public void serverException() throws Exception {
this.client.get().uri("/server-error")
.exchange()
.assertStatus().isInternalServerError();
}
@RestController
static class TestController {
@GetMapping("/server-error")
void handleAndThrowException() {
throw new IllegalStateException("server error");
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.reactive.server.samples;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
/**
* Tests with custom headers.
*
* @author Rossen Stoyanchev
*/
@SuppressWarnings("unused")
public class HeaderTests {
private WebTestClient client;
@Before
public void setUp() throws Exception {
this.client = WebTestClient.bindToController(new TestController()).build();
}
@Test
public void customHeader() throws Exception {
this.client.get().uri("/header").header("h1", "ping")
.exchange()
.assertStatus().isOk()
.assertHeader("h1").isEqualTo("ping-pong");
}
@RestController
static class TestController {
@GetMapping("header")
ResponseEntity<Void> handleHeader(@RequestHeader("h1") String myHeader) {
String value = myHeader + "-pong";
return ResponseEntity.ok().header("h1", value).build();
}
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.reactive.server.samples;
import java.net.URI;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.junit.Assert.assertThat;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
/**
* Annotated controllers accepting and returning typed Objects.
*
* @author Rossen Stoyanchev
*/
@SuppressWarnings("unused")
public class ResponseEntityTests {
private WebTestClient client;
@Before
public void setUp() throws Exception {
this.client = WebTestClient.bindToController(new PersonController()).build();
}
@Test
public void entity() throws Exception {
this.client.get().uri("/persons/John")
.exchange()
.assertStatus().isOk()
.assertHeaders().contentType(MediaType.APPLICATION_JSON_UTF8)
.assertEntity(Person.class).isEqualTo(new Person("John"));
}
@Test
public void entityCollection() throws Exception {
this.client.get().uri("/persons")
.exchange()
.assertStatus().isOk()
.assertHeaders().contentType(MediaType.APPLICATION_JSON_UTF8)
.assertEntity(Person.class).collection()
.hasSize(3)
.contains(new Person("Jane"), new Person("Jason"), new Person("John"));
}
@Test
public void entityStream() throws Exception {
this.client.get().uri("/persons").accept(TEXT_EVENT_STREAM)
.exchange()
.assertStatus().isOk()
.assertHeaders().contentType(TEXT_EVENT_STREAM)
.assertEntity(Person.class).stepVerifier()
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
.expectNextCount(4)
.consumeNextWith(person -> assertThat(person.getName(), endsWith("7")))
.thenCancel()
.verify();
}
@Test
public void saveEntity() throws Exception {
this.client.post().uri("/persons")
.exchange(Mono.just(new Person("John")), Person.class)
.assertStatus().isCreated()
.assertHeader("location").isEqualTo("/persons/John")
.assertNoContent();
}
@RestController
@RequestMapping("/persons")
static class PersonController {
@GetMapping("/{name}")
Person getPerson(@PathVariable String name) {
return new Person(name);
}
@GetMapping
Flux<Person> getPersons() {
return Flux.just(new Person("Jane"), new Person("Jason"), new Person("John"));
}
@GetMapping(produces = "text/event-stream")
Flux<Person> getPersonStream() {
return Flux.intervalMillis(100).onBackpressureBuffer(10).map(index -> new Person("N" + index));
}
@PostMapping
ResponseEntity<String> savePerson(@RequestBody Person person) {
return ResponseEntity.created(URI.create("/persons/" + person.getName())).build();
}
}
static class Person {
private final String name;
@JsonCreator
public Person(@JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Person person = (Person) other;
return getName().equals(person.getName());
}
@Override
public int hashCode() {
return getName().hashCode();
}
@Override
public String toString() {
return "Person[name='" + name + "']";
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.reactive.server.samples.bind;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.config.EnableWebFlux;
/**
* Binding to server infrastructure declared in a Spring ApplicationContext.
*
* @author Rossen Stoyanchev
*/
@SuppressWarnings("unused")
public class ApplicationContextTests {
private WebTestClient client;
@Before
public void setUp() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(WebConfig.class);
context.refresh();
this.client = WebTestClient.bindToApplicationContext(context).build();
}
@Test
public void test() throws Exception {
this.client.get().uri("/test")
.exchange()
.assertStatus().isOk()
.assertEntity(String .class).isEqualTo("It works!");
}
@Configuration
@EnableWebFlux
static class WebConfig {
@Bean
public TestController controller() {
return new TestController();
}
}
@RestController
static class TestController {
@GetMapping("/test")
public String handle() {
return "It works!";
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.reactive.server.samples.bind;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Bind to annotated controllers.
*
* @author Rossen Stoyanchev
*/
@SuppressWarnings("unused")
public class ControllerTests {
private WebTestClient client;
@Before
public void setUp() throws Exception {
this.client = WebTestClient.bindToController(new TestController()).build();
}
@Test
public void test() throws Exception {
this.client.get().uri("/test")
.exchange()
.assertStatus().isOk()
.assertEntity(String .class).isEqualTo("It works!");
}
@RestController
static class TestController {
@GetMapping("/test")
public String handle() {
return "It works!";
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.reactive.server.samples.bind;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.bootstrap.ReactorHttpServer;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
/**
* Bind to a running server, making actual requests over a socket.
*
* @author Rossen Stoyanchev
*/
public class HttpServerTests {
private ReactorHttpServer server;
private WebTestClient client;
@Before
public void setUp() throws Exception {
HttpHandler httpHandler = RouterFunctions.toHttpHandler(
route(GET("/test"), request ->
ServerResponse.ok().body(Mono.just("It works!"), String.class)));
this.server = new ReactorHttpServer();
this.server.setHandler(httpHandler);
this.server.afterPropertiesSet();
this.server.start();
this.client = WebTestClient.bindToServer()
.baseUrl("http://localhost:" + this.server.getPort())
.build();
}
@After
public void tearDown() throws Exception {
this.server.stop();
}
@Test
public void test() throws Exception {
this.client.get().uri("/test")
.exchange()
.assertStatus().isOk()
.assertEntity(String .class).isEqualTo("It works!");
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.reactive.server.samples.bind;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
/**
* Bind to a {@link RouterFunction} and functional endpoints.
*
* @author Rossen Stoyanchev
*/
public class RouterFunctionTests {
private WebTestClient testClient;
@Before
public void setUp() throws Exception {
RouterFunction<?> route = route(GET("/test"), request ->
ServerResponse.ok().body(Mono.just("It works!"), String.class));
this.testClient = WebTestClient.bindToRouterFunction(route).build();
}
@Test
public void test() throws Exception {
this.testClient.get().uri("/test")
.exchange()
.assertStatus().isOk()
.assertEntity(String .class).isEqualTo("It works!");
}
}