Add MockMvcTestClient

See gh-19647
This commit is contained in:
Rossen Stoyanchev
2020-08-19 21:12:04 +01:00
parent 128acaff8a
commit 3426e6274c
43 changed files with 5299 additions and 58 deletions

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.context;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Callable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static org.mockito.ArgumentMatchers.any;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.context.AsyncControllerJavaConfigTests}.
*
* @author Rossen Stoyanchev
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy(@ContextConfiguration(classes = AsyncControllerJavaConfigTests.WebConfig.class))
public class AsyncControllerJavaConfigTests {
@Autowired
private WebApplicationContext wac;
@Autowired
private CallableProcessingInterceptor callableInterceptor;
private WebTestClient testClient;
@BeforeEach
public void setup() {
this.testClient = MockMvcTestClient.bindToApplicationContext(this.wac).build();
}
@Test
public void callableInterceptor() throws Exception {
testClient.get().uri("/callable")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"key\":\"value\"}");
Mockito.verify(this.callableInterceptor).beforeConcurrentHandling(any(), any());
Mockito.verify(this.callableInterceptor).preProcess(any(), any());
Mockito.verify(this.callableInterceptor).postProcess(any(), any(), any());
Mockito.verify(this.callableInterceptor).afterCompletion(any(), any());
Mockito.verifyNoMoreInteractions(this.callableInterceptor);
}
@Configuration
@EnableWebMvc
static class WebConfig implements WebMvcConfigurer {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.registerCallableInterceptors(callableInterceptor());
}
@Bean
public CallableProcessingInterceptor callableInterceptor() {
return Mockito.mock(CallableProcessingInterceptor.class);
}
@Bean
public AsyncController asyncController() {
return new AsyncController();
}
}
@RestController
static class AsyncController {
@GetMapping("/callable")
public Callable<Map<String, String>> getCallable() {
return () -> Collections.singletonMap("key", "value");
}
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.context;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.test.web.servlet.samples.context.PersonController;
import org.springframework.test.web.servlet.samples.context.PersonDao;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import static org.mockito.BDDMockito.given;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.context.JavaConfigTests}.
*
* @author Rossen Stoyanchev
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("classpath:META-INF/web-resources")
@ContextHierarchy({
@ContextConfiguration(classes = JavaConfigTests.RootConfig.class),
@ContextConfiguration(classes = JavaConfigTests.WebConfig.class)
})
public class JavaConfigTests {
@Autowired
private WebApplicationContext wac;
@Autowired
private PersonDao personDao;
@Autowired
private PersonController personController;
private WebTestClient testClient;
@BeforeEach
public void setup() {
this.testClient = MockMvcTestClient.bindToApplicationContext(this.wac).build();
given(this.personDao.getPerson(5L)).willReturn(new Person("Joe"));
}
@Test
public void person() {
testClient.get().uri("/person/5")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Test
public void tilesDefinitions() {
testClient.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Forwarded-Url", "/WEB-INF/layouts/standardLayout.jsp");
}
@Configuration
static class RootConfig {
@Bean
public PersonDao personDao() {
return Mockito.mock(PersonDao.class);
}
}
@Configuration
@EnableWebMvc
static class WebConfig implements WebMvcConfigurer {
@Autowired
private RootConfig rootConfig;
@Bean
public PersonController personController() {
return new PersonController(this.rootConfig.personDao());
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.tiles();
}
@Bean
public TilesConfigurer tilesConfigurer() {
TilesConfigurer configurer = new TilesConfigurer();
configurer.setDefinitions("/WEB-INF/**/tiles.xml");
return configurer;
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.context;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.handler;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.context.WebAppResourceTests}.
*
* @author Rossen Stoyanchev
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("src/test/resources/META-INF/web-resources")
@ContextHierarchy({
@ContextConfiguration("../../context/root-context.xml"),
@ContextConfiguration("../../context/servlet-context.xml")
})
public class WebAppResourceTests {
@Autowired
private WebApplicationContext wac;
private WebTestClient testClient;
@BeforeEach
public void setup() {
this.testClient = MockMvcTestClient.bindToApplicationContext(this.wac).build();
}
// TilesConfigurer: resources under "/WEB-INF/**/tiles.xml"
@Test
public void tilesDefinitions() {
testClient.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Forwarded-Url", "/WEB-INF/layouts/standardLayout.jsp");
}
// Resources served via <mvc:resources/>
@Test
public void resourceRequest() {
testClient.get().uri("/resources/Spring.js")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("application/javascript")
.expectBody(String.class).value(containsString("Spring={};"));
}
// Forwarded to the "default" servlet via <mvc:default-servlet-handler/>
@Test
public void resourcesViaDefaultServlet() throws Exception {
EntityExchangeResult<Void> result = testClient.get().uri("/unknown/resource")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
MockMvcTestClient.resultActionsFor(result)
.andExpect(handler().handlerType(DefaultServletHttpRequestHandler.class))
.andExpect(forwardedUrl("default"));
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.context;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.test.web.servlet.samples.context.PersonDao;
import org.springframework.web.context.WebApplicationContext;
import static org.mockito.BDDMockito.given;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.context.XmlConfigTests}.
*
* @author Rossen Stoyanchev
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("src/test/resources/META-INF/web-resources")
@ContextHierarchy({
@ContextConfiguration("../../context/root-context.xml"),
@ContextConfiguration("../../context/servlet-context.xml")
})
public class XmlConfigTests {
@Autowired
private WebApplicationContext wac;
@Autowired
private PersonDao personDao;
private WebTestClient testClient;
@BeforeEach
public void setup() {
this.testClient = MockMvcTestClient.bindToApplicationContext(this.wac).build();
given(this.personDao.getPerson(5L)).willReturn(new Person("Joe"));
}
@Test
public void person() {
testClient.get().uri("/person/5")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Test
public void tilesDefinitions() {
testClient.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Forwarded-Url", "/WEB-INF/layouts/standardLayout.jsp");
}
}

View File

@@ -0,0 +1,221 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureTask;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.AsyncTests}.
*
* @author Rossen Stoyanchev
*/
public class AsyncTests {
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new AsyncController()).build();
@Test
public void callable() {
this.testClient.get()
.uri("/1?callable=true")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Test
public void streaming() {
this.testClient.get()
.uri("/1?streaming=true")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("name=Joe");
}
@Test
public void streamingSlow() {
this.testClient.get()
.uri("/1?streamingSlow=true")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("name=Joe&someBoolean=true");
}
@Test
public void streamingJson() {
this.testClient.get()
.uri("/1?streamingJson=true")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.5}");
}
@Test
public void deferredResult() {
this.testClient.get()
.uri("/1?deferredResult=true")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Test
public void deferredResultWithImmediateValue() throws Exception {
this.testClient.get()
.uri("/1?deferredResultWithImmediateValue=true")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Test
public void deferredResultWithDelayedError() throws Exception {
this.testClient.get()
.uri("/1?deferredResultWithDelayedError=true")
.exchange()
.expectStatus().is5xxServerError()
.expectBody(String.class).isEqualTo("Delayed Error");
}
@Test
public void listenableFuture() throws Exception {
this.testClient.get()
.uri("/1?listenableFuture=true")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Test
public void completableFutureWithImmediateValue() throws Exception {
this.testClient.get()
.uri("/1?completableFutureWithImmediateValue=true")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@RestController
@RequestMapping(path = "/{id}", produces = "application/json")
private static class AsyncController {
@RequestMapping(params = "callable")
public Callable<Person> getCallable() {
return () -> new Person("Joe");
}
@RequestMapping(params = "streaming")
public StreamingResponseBody getStreaming() {
return os -> os.write("name=Joe".getBytes(StandardCharsets.UTF_8));
}
@RequestMapping(params = "streamingSlow")
public StreamingResponseBody getStreamingSlow() {
return os -> {
os.write("name=Joe".getBytes());
try {
Thread.sleep(200);
os.write("&someBoolean=true".getBytes(StandardCharsets.UTF_8));
}
catch (InterruptedException e) {
/* no-op */
}
};
}
@RequestMapping(params = "streamingJson")
public ResponseEntity<StreamingResponseBody> getStreamingJson() {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON)
.body(os -> os.write("{\"name\":\"Joe\",\"someDouble\":0.5}".getBytes(StandardCharsets.UTF_8)));
}
@RequestMapping(params = "deferredResult")
public DeferredResult<Person> getDeferredResult() {
DeferredResult<Person> result = new DeferredResult<>();
delay(100, () -> result.setResult(new Person("Joe")));
return result;
}
@RequestMapping(params = "deferredResultWithImmediateValue")
public DeferredResult<Person> getDeferredResultWithImmediateValue() {
DeferredResult<Person> result = new DeferredResult<>();
result.setResult(new Person("Joe"));
return result;
}
@RequestMapping(params = "deferredResultWithDelayedError")
public DeferredResult<Person> getDeferredResultWithDelayedError() {
DeferredResult<Person> result = new DeferredResult<>();
delay(100, () -> result.setErrorResult(new RuntimeException("Delayed Error")));
return result;
}
@RequestMapping(params = "listenableFuture")
public ListenableFuture<Person> getListenableFuture() {
ListenableFutureTask<Person> futureTask = new ListenableFutureTask<>(() -> new Person("Joe"));
delay(100, futureTask);
return futureTask;
}
@RequestMapping(params = "completableFutureWithImmediateValue")
public CompletableFuture<Person> getCompletableFutureWithImmediateValue() {
CompletableFuture<Person> future = new CompletableFuture<>();
future.complete(new Person("Joe"));
return future;
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String errorHandler(Exception ex) {
return ex.getMessage();
}
private void delay(long millis, Runnable task) {
Mono.delay(Duration.ofMillis(millis)).doOnTerminate(task).subscribe();
}
}
}

View File

@@ -0,0 +1,238 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.ExceptionHandlerTests}.
*
* @author Rossen Stoyanchev
*/
class ExceptionHandlerTests {
@Nested
class MvcTests {
@Test
void localExceptionHandlerMethod() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController()).build();
client.get().uri("/person/Clyde")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Forwarded-Url", "errorView");
}
@Test
void globalExceptionHandlerMethod() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.controllerAdvice(new GlobalExceptionHandler())
.build();
client.get().uri("/person/Bonnie")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Forwarded-Url", "globalErrorView");
}
}
@Controller
private static class PersonController {
@GetMapping("/person/{name}")
String show(@PathVariable String name) {
if (name.equals("Clyde")) {
throw new IllegalArgumentException("simulated exception");
}
else if (name.equals("Bonnie")) {
throw new IllegalStateException("simulated exception");
}
return "person/show";
}
@ExceptionHandler
String handleException(IllegalArgumentException exception) {
return "errorView";
}
}
@ControllerAdvice
private static class GlobalExceptionHandler {
@ExceptionHandler
String handleException(IllegalStateException exception) {
return "globalErrorView";
}
}
@Nested
class RestTests {
@Test
void noException() {
WebTestClient client = MockMvcTestClient.bindToController(new RestPersonController())
.controllerAdvice(new RestPersonControllerExceptionHandler())
.build();
client.get().uri("/person/Yoda")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.name", "Yoda");
}
@Test
void localExceptionHandlerMethod() {
WebTestClient client = MockMvcTestClient.bindToController(new RestPersonController())
.controllerAdvice(new RestPersonControllerExceptionHandler())
.build();
client.get().uri("/person/Luke")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.error", "local - IllegalArgumentException");
}
@Test
void globalExceptionHandlerMethod() {
WebTestClient client = MockMvcTestClient.bindToController(new RestPersonController())
.controllerAdvice(new RestGlobalExceptionHandler())
.build();
client.get().uri("/person/Leia")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.error", "global - IllegalArgumentException");
}
@Test
void globalRestPersonControllerExceptionHandlerTakesPrecedenceOverGlobalExceptionHandler() {
WebTestClient client = MockMvcTestClient.bindToController(new RestPersonController())
.controllerAdvice(RestGlobalExceptionHandler.class, RestPersonControllerExceptionHandler.class)
.build();
client.get().uri("/person/Leia")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.error", "globalPersonController - IllegalStateException");
}
@Test
void noHandlerFound() {
WebTestClient client = MockMvcTestClient.bindToController(new RestPersonController())
.controllerAdvice(RestGlobalExceptionHandler.class, RestPersonControllerExceptionHandler.class)
.dispatcherServletCustomizer(servlet -> servlet.setThrowExceptionIfNoHandlerFound(true))
.build();
client.get().uri("/bogus")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.error", "global - NoHandlerFoundException");
}
}
@RestController
private static class RestPersonController {
@GetMapping("/person/{name}")
Person get(@PathVariable String name) {
switch (name) {
case "Luke":
throw new IllegalArgumentException();
case "Leia":
throw new IllegalStateException();
default:
return new Person("Yoda");
}
}
@ExceptionHandler
Error handleException(IllegalArgumentException exception) {
return new Error("local - " + exception.getClass().getSimpleName());
}
}
@RestControllerAdvice(assignableTypes = RestPersonController.class)
@Order(Ordered.HIGHEST_PRECEDENCE)
private static class RestPersonControllerExceptionHandler {
@ExceptionHandler
Error handleException(Throwable exception) {
return new Error("globalPersonController - " + exception.getClass().getSimpleName());
}
}
@RestControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
private static class RestGlobalExceptionHandler {
@ExceptionHandler
Error handleException(Throwable exception) {
return new Error( "global - " + exception.getClass().getSimpleName());
}
}
static class Person {
private final String name;
Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
static class Error {
private final String error;
Error(String error) {
this.error = error;
}
public String getError() {
return error;
}
}
}

View File

@@ -0,0 +1,323 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone;
import java.io.IOException;
import java.security.Principal;
import java.util.concurrent.CompletableFuture;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncListener;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.validation.Valid;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.flash;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.FilterTests}.
*
* @author Rossen Stoyanchev
*/
public class FilterTests {
@Test
public void whenFiltersCompleteMvcProcessesRequest() throws Exception {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filters(new ContinueFilter())
.build();
EntityExchangeResult<Void> exchangeResult = client.post().uri("/persons?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/person/1")
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().size(1))
.andExpect(model().attributeExists("id"))
.andExpect(flash().attributeCount(1))
.andExpect(flash().attribute("message", "success!"));
}
@Test
public void filtersProcessRequest() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filters(new ContinueFilter(), new RedirectFilter())
.build();
client.post().uri("/persons?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/login");
}
@Test
public void filterMappedBySuffix() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filter(new RedirectFilter(), "*.html")
.build();
client.post().uri("/persons.html?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/login");
}
@Test
public void filterWithExactMapping() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filter(new RedirectFilter(), "/p", "/persons")
.build();
client.post().uri("/persons?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/login");
}
@Test
public void filterSkipped() throws Exception {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filter(new RedirectFilter(), "/p", "/person")
.build();
EntityExchangeResult<Void> exchangeResult =
client.post().uri("/persons?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/person/1")
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().size(1))
.andExpect(model().attributeExists("id"))
.andExpect(flash().attributeCount(1))
.andExpect(flash().attribute("message", "success!"));
}
@Test
public void filterWrapsRequestResponse() throws Exception {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filter(new WrappingRequestResponseFilter())
.build();
EntityExchangeResult<Void> exchangeResult =
client.post().uri("/user").exchange().expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("principal", WrappingRequestResponseFilter.PRINCIPAL_NAME));
}
@Test
public void filterWrapsRequestResponseAndPerformsAsyncDispatch() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController())
.filters(new WrappingRequestResponseFilter(), new ShallowEtagHeaderFilter())
.build();
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentLength(53)
.expectHeader().valueEquals("ETag", "\"0e37becb4f0c90709cb2e1efcc61eaa00\"")
.expectBody().json("{\"name\":\"Lukas\",\"someDouble\":0.0,\"someBoolean\":false}");
}
@Controller
private static class PersonController {
@PostMapping(path="/persons")
public String save(@Valid Person person, Errors errors, RedirectAttributes redirectAttrs) {
if (errors.hasErrors()) {
return "person/add";
}
redirectAttrs.addAttribute("id", "1");
redirectAttrs.addFlashAttribute("message", "success!");
return "redirect:/person/{id}";
}
@PostMapping("/user")
public ModelAndView user(Principal principal) {
return new ModelAndView("user/view", "principal", principal.getName());
}
@GetMapping("/forward")
public String forward() {
return "forward:/persons";
}
@GetMapping("persons/{id}")
@ResponseBody
public CompletableFuture<Person> getPerson() {
return CompletableFuture.completedFuture(new Person("Lukas"));
}
}
private class ContinueFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
filterChain.doFilter(request, response);
}
}
private static class WrappingRequestResponseFilter extends OncePerRequestFilter {
public static final String PRINCIPAL_NAME = "WrapRequestResponseFilterPrincipal";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
filterChain.doFilter(new HttpServletRequestWrapper(request) {
@Override
public Principal getUserPrincipal() {
return () -> PRINCIPAL_NAME;
}
// Like Spring Security does in HttpServlet3RequestFactory..
@Override
public AsyncContext getAsyncContext() {
return super.getAsyncContext() != null ?
new AsyncContextWrapper(super.getAsyncContext()) : null;
}
}, new HttpServletResponseWrapper(response));
}
}
private class RedirectFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
response.sendRedirect("/login");
}
}
private static class AsyncContextWrapper implements AsyncContext {
private final AsyncContext delegate;
public AsyncContextWrapper(AsyncContext delegate) {
this.delegate = delegate;
}
@Override
public ServletRequest getRequest() {
return this.delegate.getRequest();
}
@Override
public ServletResponse getResponse() {
return this.delegate.getResponse();
}
@Override
public boolean hasOriginalRequestAndResponse() {
return this.delegate.hasOriginalRequestAndResponse();
}
@Override
public void dispatch() {
this.delegate.dispatch();
}
@Override
public void dispatch(String path) {
this.delegate.dispatch(path);
}
@Override
public void dispatch(ServletContext context, String path) {
this.delegate.dispatch(context, path);
}
@Override
public void complete() {
this.delegate.complete();
}
@Override
public void start(Runnable run) {
this.delegate.start(run);
}
@Override
public void addListener(AsyncListener listener) {
this.delegate.addListener(listener);
}
@Override
public void addListener(AsyncListener listener, ServletRequest req, ServletResponse res) {
this.delegate.addListener(listener, req, res);
}
@Override
public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException {
return this.delegate.createListener(clazz);
}
@Override
public void setTimeout(long timeout) {
this.delegate.setTimeout(timeout);
}
@Override
public long getTimeout() {
return this.delegate.getTimeout();
}
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone;
import java.security.Principal;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurerAdapter;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.WebApplicationContext;
import static org.mockito.Mockito.mock;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.FrameworkExtensionTests}.
*
* @author Rossen Stoyanchev
*/
public class FrameworkExtensionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new SampleController())
.apply(defaultSetup())
.build();
@Test
public void fooHeader() {
this.client.get().uri("/")
.header("Foo", "a=b")
.exchange()
.expectBody(String.class).isEqualTo("Foo");
}
@Test
public void barHeader() {
this.client.get().uri("/")
.header("Bar", "a=b")
.exchange()
.expectBody(String.class).isEqualTo("Bar");
}
private static TestMockMvcConfigurer defaultSetup() {
return new TestMockMvcConfigurer();
}
/**
* Test {@code MockMvcConfigurer}.
*/
private static class TestMockMvcConfigurer extends MockMvcConfigurerAdapter {
@Override
public void afterConfigurerAdded(ConfigurableMockMvcBuilder<?> builder) {
builder.alwaysExpect(status().isOk());
}
@Override
public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder<?> builder,
WebApplicationContext context) {
return request -> {
request.setUserPrincipal(mock(Principal.class));
return request;
};
}
}
@Controller
@RequestMapping("/")
private static class SampleController {
@RequestMapping(headers = "Foo")
@ResponseBody
public String handleFoo(Principal principal) {
Assert.notNull(principal, "Principal must not be null");
return "Foo";
}
@RequestMapping(headers = "Bar")
@ResponseBody
public String handleBar(Principal principal) {
Assert.notNull(principal, "Principal must not be null");
return "Bar";
}
}
}

View File

@@ -0,0 +1,405 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.multipart.MultipartFile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.MultipartControllerTests}.
*
* @author Rossen Stoyanchev
*/
public class MultipartControllerTests {
private final WebTestClient testClient = MockMvcTestClient.bindToController(new MultipartController()).build();
@Test
public void multipartRequestWithSingleFile() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/multipartfile")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithSingleFileNotPresent() {
testClient.post().uri("/multipartfile")
.exchange()
.expectStatus().isFound();
}
@Test
public void multipartRequestWithFileArray() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/multipartfilearray")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithFileArrayNoMultipart() {
testClient.post().uri("/multipartfilearray")
.exchange()
.expectStatus().isFound();
}
@Test
public void multipartRequestWithOptionalFile() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/optionalfile")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithOptionalFileNotPresent() throws Exception {
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/optionalfile")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attributeDoesNotExist("fileContent"))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithOptionalFileArray() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/optionalfilearray")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithOptionalFileArrayNotPresent() throws Exception {
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/optionalfilearray")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attributeDoesNotExist("fileContent"))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithOptionalFileList() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/optionalfilelist")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithOptionalFileListNotPresent() throws Exception {
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/optionalfilelist")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attributeDoesNotExist("fileContent"))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWithServletParts() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", fileContent).filename("orig");
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
EntityExchangeResult<Void> exchangeResult = testClient.post().uri("/multipartfile")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", json));
}
@Test
public void multipartRequestWrapped() throws Exception {
Map<String, String> json = Collections.singletonMap("name", "yeeeah");
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("json", json, MediaType.APPLICATION_JSON);
WebTestClient client = MockMvcTestClient.bindToController(new MultipartController())
.filter(new RequestWrappingFilter())
.build();
EntityExchangeResult<Void> exchangeResult = client.post().uri("/multipartfile")
.bodyValue(bodyBuilder.build())
.exchange()
.expectStatus().isFound()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().attribute("jsonContent", json));
}
@Controller
private static class MultipartController {
@RequestMapping(value = "/multipartfile", method = RequestMethod.POST)
public String processMultipartFile(@RequestParam(required = false) MultipartFile file,
@RequestPart(required = false) Map<String, String> json, Model model) throws IOException {
if (file != null) {
model.addAttribute("fileContent", file.getBytes());
}
if (json != null) {
model.addAttribute("jsonContent", json);
}
return "redirect:/index";
}
@RequestMapping(value = "/multipartfilearray", method = RequestMethod.POST)
public String processMultipartFileArray(@RequestParam(required = false) MultipartFile[] file,
@RequestPart(required = false) Map<String, String> json, Model model) throws IOException {
if (file != null && file.length > 0) {
byte[] content = file[0].getBytes();
assertThat(file[1].getBytes()).isEqualTo(content);
model.addAttribute("fileContent", content);
}
if (json != null) {
model.addAttribute("jsonContent", json);
}
return "redirect:/index";
}
@RequestMapping(value = "/multipartfilelist", method = RequestMethod.POST)
public String processMultipartFileList(@RequestParam(required = false) List<MultipartFile> file,
@RequestPart(required = false) Map<String, String> json, Model model) throws IOException {
if (file != null && !file.isEmpty()) {
byte[] content = file.get(0).getBytes();
assertThat(file.get(1).getBytes()).isEqualTo(content);
model.addAttribute("fileContent", content);
}
if (json != null) {
model.addAttribute("jsonContent", json);
}
return "redirect:/index";
}
@RequestMapping(value = "/optionalfile", method = RequestMethod.POST)
public String processOptionalFile(@RequestParam Optional<MultipartFile> file,
@RequestPart Map<String, String> json, Model model) throws IOException {
if (file.isPresent()) {
model.addAttribute("fileContent", file.get().getBytes());
}
model.addAttribute("jsonContent", json);
return "redirect:/index";
}
@RequestMapping(value = "/optionalfilearray", method = RequestMethod.POST)
public String processOptionalFileArray(@RequestParam Optional<MultipartFile[]> file,
@RequestPart Map<String, String> json, Model model) throws IOException {
if (file.isPresent()) {
byte[] content = file.get()[0].getBytes();
assertThat(file.get()[1].getBytes()).isEqualTo(content);
model.addAttribute("fileContent", content);
}
model.addAttribute("jsonContent", json);
return "redirect:/index";
}
@RequestMapping(value = "/optionalfilelist", method = RequestMethod.POST)
public String processOptionalFileList(@RequestParam Optional<List<MultipartFile>> file,
@RequestPart Map<String, String> json, Model model) throws IOException {
if (file.isPresent()) {
byte[] content = file.get().get(0).getBytes();
assertThat(file.get().get(1).getBytes()).isEqualTo(content);
model.addAttribute("fileContent", content);
}
model.addAttribute("jsonContent", json);
return "redirect:/index";
}
@RequestMapping(value = "/part", method = RequestMethod.POST)
public String processPart(@RequestParam Part part,
@RequestPart Map<String, String> json, Model model) throws IOException {
model.addAttribute("fileContent", part.getInputStream());
model.addAttribute("jsonContent", json);
return "redirect:/index";
}
@RequestMapping(value = "/json", method = RequestMethod.POST)
public String processMultipart(@RequestPart Map<String, String> json, Model model) {
model.addAttribute("json", json);
return "redirect:/index";
}
}
private static class RequestWrappingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
request = new HttpServletRequestWrapper(request);
filterChain.doFilter(request, response);
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.ReactiveReturnTypeTests}.
*
* @author Rossen Stoyanchev
*/
public class ReactiveReturnTypeTests {
@Test
public void sseWithFlux() {
WebTestClient testClient =
MockMvcTestClient.bindToController(new ReactiveController()).build();
Flux<String> bodyFlux = testClient.get().uri("/spr16869")
.exchange()
.expectStatus().isOk()
.expectHeader().contentTypeCompatibleWith(TEXT_EVENT_STREAM)
.returnResult(String.class)
.getResponseBody();
StepVerifier.create(bodyFlux)
.expectNext("event0")
.expectNext("event1")
.expectNext("event2")
.verifyComplete();
}
@RestController
static class ReactiveController {
@GetMapping(path = "/spr16869", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
Flux<String> sseFlux() {
return Flux.interval(Duration.ofSeconds(1)).take(3)
.map(aLong -> String.format("event%d", aLong));
}
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone;
import javax.validation.Valid;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
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.servlet.mvc.support.RedirectAttributes;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.flash;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.RedirectTests}.
*
* @author Rossen Stoyanchev
*/
public class RedirectTests {
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new PersonController()).build();
@Test
public void save() throws Exception {
EntityExchangeResult<Void> exchangeResult =
testClient.post().uri("/persons?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/persons/Joe")
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(exchangeResult)
.andExpect(model().size(1))
.andExpect(model().attributeExists("name"))
.andExpect(flash().attributeCount(1))
.andExpect(flash().attribute("message", "success!"));
}
@Test
public void saveSpecial() throws Exception {
EntityExchangeResult<Void> result =
testClient.post().uri("/people?name=Andy")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/persons/Joe")
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(result)
.andExpect(model().size(1))
.andExpect(model().attributeExists("name"))
.andExpect(flash().attributeCount(1))
.andExpect(flash().attribute("message", "success!"));
}
@Test
public void saveWithErrors() throws Exception {
EntityExchangeResult<Void> result =
testClient.post().uri("/persons").exchange().expectStatus().isOk().expectBody().isEmpty();
MockMvcTestClient.resultActionsFor(result)
.andExpect(forwardedUrl("persons/add"))
.andExpect(model().size(1))
.andExpect(model().attributeExists("person"))
.andExpect(flash().attributeCount(0));
}
@Test
public void saveSpecialWithErrors() throws Exception {
EntityExchangeResult<Void> result =
testClient.post().uri("/people").exchange().expectStatus().isOk().expectBody().isEmpty();
MockMvcTestClient.resultActionsFor(result)
.andExpect(forwardedUrl("persons/add"))
.andExpect(model().size(1))
.andExpect(model().attributeExists("person"))
.andExpect(flash().attributeCount(0));
}
@Test
public void getPerson() throws Exception {
EntityExchangeResult<Void> result =
MockMvcTestClient.bindToController(new PersonController())
.defaultRequest(get("/").flashAttr("message", "success!"))
.build()
.get().uri("/persons/Joe")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(result)
.andDo(MockMvcResultHandlers.print())
.andExpect(forwardedUrl("persons/index"))
.andExpect(model().size(2))
.andExpect(model().attribute("person", new Person("Joe")))
.andExpect(model().attribute("message", "success!"))
.andExpect(flash().attributeCount(0));
}
@Controller
private static class PersonController {
@GetMapping("/persons/{name}")
public String getPerson(@PathVariable String name, Model model) {
model.addAttribute(new Person(name));
return "persons/index";
}
@PostMapping("/persons")
public String save(@Valid Person person, Errors errors, RedirectAttributes redirectAttrs) {
if (errors.hasErrors()) {
return "persons/add";
}
redirectAttrs.addAttribute("name", "Joe");
redirectAttrs.addFlashAttribute("message", "success!");
return "redirect:/persons/{name}";
}
@PostMapping("/people")
public Object saveSpecial(@Valid Person person, Errors errors, RedirectAttributes redirectAttrs) {
if (errors.hasErrors()) {
return "persons/add";
}
redirectAttrs.addAttribute("name", "Joe");
redirectAttrs.addFlashAttribute("message", "success!");
return new StringBuilder("redirect:").append("/persons").append("/{name}");
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.RequestParameterTests}.
*
* @author Rossen Stoyanchev
*/
public class RequestParameterTests {
@Test
public void queryParameter() {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController()).build();
client.get().uri("/search?name=George")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody().jsonPath("$.name", "George");
}
@Controller
private class PersonController {
@RequestMapping(value="/search")
@ResponseBody
public Person get(@RequestParam String name) {
Person person = new Person(name);
return person;
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone;
import javax.validation.constraints.NotNull;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import static org.hamcrest.Matchers.equalTo;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.ResponseBodyTests}.
*
* @author Rossen Stoyanchev
*/
public class ResponseBodyTests {
@Test
void json() {
MockMvcTestClient.bindToController(new PersonController()).build()
.get()
.uri("/person/Lee")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody()
.jsonPath("$.name").isEqualTo("Lee")
.jsonPath("$.age").isEqualTo(42)
.jsonPath("$.age").value(equalTo(42))
.jsonPath("$.age").value(equalTo(42.0f), Float.class);
}
@RestController
private static class PersonController {
@GetMapping("/person/{name}")
public Person get(@PathVariable String name) {
Person person = new Person(name);
person.setAge(42);
return person;
}
}
private static class Person {
@NotNull
private final String name;
private int age;
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.ui.Model;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.FixedContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import org.springframework.web.servlet.view.xml.MarshallingView;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasProperty;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.RequestParameterTests}.
*
* @author Rossen Stoyanchev
*/
class ViewResolutionTests {
@Test
void jspOnly() throws Exception {
WebTestClient testClient =
MockMvcTestClient.bindToController(new PersonController())
.viewResolvers(new InternalResourceViewResolver("/WEB-INF/", ".jsp"))
.build();
EntityExchangeResult<Void> result = testClient.get().uri("/person/Corea")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(result)
.andExpect(status().isOk())
.andExpect(model().size(1))
.andExpect(model().attributeExists("person"))
.andExpect(forwardedUrl("/WEB-INF/person/show.jsp"));
}
@Test
void jsonOnly() {
WebTestClient testClient =
MockMvcTestClient.bindToController(new PersonController())
.singleView(new MappingJackson2JsonView())
.build();
testClient.get().uri("/person/Corea")
.exchange()
.expectStatus().isOk()
.expectHeader().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)
.expectBody().jsonPath("$.person.name", "Corea");
}
@Test
void xmlOnly() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(Person.class);
WebTestClient testClient =
MockMvcTestClient.bindToController(new PersonController())
.singleView(new MarshallingView(marshaller))
.build();
testClient.get().uri("/person/Corea")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_XML)
.expectBody().xpath("/person/name/text()").isEqualTo("Corea");
}
@Test
void contentNegotiation() throws Exception {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(Person.class);
List<View> viewList = new ArrayList<>();
viewList.add(new MappingJackson2JsonView());
viewList.add(new MarshallingView(marshaller));
ContentNegotiationManager manager = new ContentNegotiationManager(
new HeaderContentNegotiationStrategy(), new FixedContentNegotiationStrategy(MediaType.TEXT_HTML));
ContentNegotiatingViewResolver cnViewResolver = new ContentNegotiatingViewResolver();
cnViewResolver.setDefaultViews(viewList);
cnViewResolver.setContentNegotiationManager(manager);
cnViewResolver.afterPropertiesSet();
WebTestClient testClient =
MockMvcTestClient.bindToController(new PersonController())
.viewResolvers(cnViewResolver, new InternalResourceViewResolver())
.build();
EntityExchangeResult<Void> result = testClient.get().uri("/person/Corea")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(result)
.andExpect(model().size(1))
.andExpect(model().attributeExists("person"))
.andExpect(forwardedUrl("person/show"));
testClient.get().uri("/person/Corea")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)
.expectBody().jsonPath("$.person.name", "Corea");
testClient.get().uri("/person/Corea")
.accept(MediaType.APPLICATION_XML)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_XML)
.expectBody().xpath("/person/name/text()").isEqualTo("Corea");
}
@Test
void defaultViewResolver() throws Exception {
WebTestClient client = MockMvcTestClient.bindToController(new PersonController()).build();
EntityExchangeResult<Void> result = client.get().uri("/person/Corea")
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
// Further assertions on the server response
MockMvcTestClient.resultActionsFor(result)
.andExpect(model().attribute("person", hasProperty("name", equalTo("Corea"))))
.andExpect(forwardedUrl("person/show")); // InternalResourceViewResolver
}
@Controller
private static class PersonController {
@GetMapping("/person/{name}")
String show(@PathVariable String name, Model model) {
Person person = new Person(name);
model.addAttribute(person);
return "person/show";
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resulthandlers;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resulthandlers.PrintingResultHandlerSmokeTests}.
*
* @author Rossen Stoyanchev
*/
@Disabled
public class PrintingResultHandlerSmokeTests {
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new SimpleController()).build();
// Not intended to be executed with the build.
// Comment out class-level @Disabled to see the output.
@Test
public void printViaConsumer() {
testClient.post().uri("/")
.contentType(MediaType.TEXT_PLAIN)
.bodyValue("Hello Request".getBytes(StandardCharsets.UTF_8))
.exchange()
.expectStatus().isOk()
.expectBody(String.class)
.consumeWith(System.out::println);
}
@Test
public void returnResultAndPrint() {
EntityExchangeResult<String> result = testClient.post().uri("/")
.contentType(MediaType.TEXT_PLAIN)
.bodyValue("Hello Request".getBytes(StandardCharsets.UTF_8))
.exchange()
.expectStatus().isOk()
.expectBody(String.class)
.returnResult();
System.out.println(result);
}
@RestController
private static class SimpleController {
@PostMapping("/")
public String hello(HttpServletResponse response) {
response.addCookie(new Cookie("enigma", "42"));
return "Hello Response";
}
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.ContentAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class ContentAssertionTests {
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new SimpleController()).build();
@Test
public void testContentType() {
testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.valueOf("text/plain;charset=ISO-8859-1"))
.expectHeader().contentType("text/plain;charset=ISO-8859-1")
.expectHeader().contentTypeCompatibleWith("text/plain")
.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_PLAIN);
testClient.get().uri("/handleUtf8")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.valueOf("text/plain;charset=UTF-8"))
.expectHeader().contentType("text/plain;charset=UTF-8")
.expectHeader().contentTypeCompatibleWith("text/plain")
.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_PLAIN);
}
@Test
public void testContentAsString() {
testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello world!");
testClient.get().uri("/handleUtf8").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01");
// Hamcrest matchers...
testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(String.class).value(equalTo("Hello world!"));
testClient.get().uri("/handleUtf8")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).value(equalTo("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01"));
}
@Test
public void testContentAsBytes() {
testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(byte[].class).isEqualTo(
"Hello world!".getBytes(StandardCharsets.ISO_8859_1));
testClient.get().uri("/handleUtf8")
.exchange()
.expectStatus().isOk()
.expectBody(byte[].class).isEqualTo(
"\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes(StandardCharsets.UTF_8));
}
@Test
public void testContentStringMatcher() {
testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(String.class).value(containsString("world"));
}
@Test
public void testCharacterEncoding() {
testClient.get().uri("/handle").accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("text/plain;charset=ISO-8859-1")
.expectBody(String.class).value(containsString("world"));
testClient.get().uri("/handleUtf8")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("text/plain;charset=UTF-8")
.expectBody(byte[].class)
.isEqualTo("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes(StandardCharsets.UTF_8));
}
@Controller
private static class SimpleController {
@RequestMapping(value="/handle", produces="text/plain")
@ResponseBody
public String handle() {
return "Hello world!";
}
@RequestMapping(value="/handleUtf8", produces="text/plain;charset=UTF-8")
@ResponseBody
public String handleWithCharset() {
return "\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01"; // "Hello world! (Japanese)
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import java.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.CookieAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class CookieAssertionTests {
private static final String COOKIE_NAME = CookieLocaleResolver.DEFAULT_COOKIE_NAME;
private WebTestClient client;
@BeforeEach
public void setup() {
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setCookieDomain("domain");
localeResolver.setCookieHttpOnly(true);
client = MockMvcTestClient.bindToController(new SimpleController())
.interceptors(new LocaleChangeInterceptor())
.localeResolver(localeResolver)
.alwaysExpect(status().isOk())
.configureClient()
.baseUrl("/?locale=en_US")
.build();
}
@Test
public void testExists() {
client.get().uri("/").exchange().expectCookie().exists(COOKIE_NAME);
}
@Test
public void testNotExists() {
client.get().uri("/").exchange().expectCookie().doesNotExist("unknownCookie");
}
@Test
public void testEqualTo() {
client.get().uri("/").exchange().expectCookie().valueEquals(COOKIE_NAME, "en-US");
client.get().uri("/").exchange().expectCookie().value(COOKIE_NAME, equalTo("en-US"));
}
@Test
public void testMatcher() {
client.get().uri("/").exchange().expectCookie().value(COOKIE_NAME, startsWith("en-US"));
}
@Test
public void testMaxAge() {
client.get().uri("/").exchange().expectCookie().maxAge(COOKIE_NAME, Duration.ofSeconds(-1));
}
@Test
public void testDomain() {
client.get().uri("/").exchange().expectCookie().domain(COOKIE_NAME, "domain");
}
@Test
public void testPath() {
client.get().uri("/").exchange().expectCookie().path(COOKIE_NAME, "/");
}
@Test
public void testSecured() {
client.get().uri("/").exchange().expectCookie().secure(COOKIE_NAME, false);
}
@Test
public void testHttpOnly() {
client.get().uri("/").exchange().expectCookie().httpOnly(COOKIE_NAME, true);
}
@Controller
private static class SimpleController {
@RequestMapping("/")
public String home() {
return "home";
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import java.net.URL;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.Matchers.closeTo;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.flash;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.FlashAttributeAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class FlashAttributeAssertionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new PersonController())
.alwaysExpect(status().isFound())
.alwaysExpect(flash().attributeCount(3))
.build();
@Test
void attributeCountWithWrongCount() {
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> performRequest().andExpect(flash().attributeCount(1)))
.withMessage("FlashMap size expected:<1> but was:<3>");
}
@Test
void attributeExists() throws Exception {
performRequest().andExpect(flash().attributeExists("one", "two", "three"));
}
@Test
void attributeEqualTo() throws Exception {
performRequest()
.andExpect(flash().attribute("one", "1"))
.andExpect(flash().attribute("two", 2.222))
.andExpect(flash().attribute("three", new URL("https://example.com")));
}
@Test
void attributeMatchers() throws Exception {
performRequest()
.andExpect(flash().attribute("one", containsString("1")))
.andExpect(flash().attribute("two", closeTo(2, 0.5)))
.andExpect(flash().attribute("three", notNullValue()))
.andExpect(flash().attribute("one", equalTo("1")))
.andExpect(flash().attribute("two", equalTo(2.222)))
.andExpect(flash().attribute("three", equalTo(new URL("https://example.com"))));
}
private ResultActions performRequest() {
EntityExchangeResult<Void> result = client.post().uri("/persons").exchange().expectBody().isEmpty();
return MockMvcTestClient.resultActionsFor(result);
}
@Controller
private static class PersonController {
@PostMapping("/persons")
String save(RedirectAttributes redirectAttrs) throws Exception {
redirectAttrs.addFlashAttribute("one", "1");
redirectAttrs.addFlashAttribute("two", 2.222);
redirectAttrs.addFlashAttribute("three", new URL("https://example.com"));
return "redirect:/person/1";
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.handler;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.on;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.HandlerAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class HandlerAssertionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new SimpleController())
.alwaysExpect(status().isOk())
.build();
@Test
public void handlerType() throws Exception {
performRequest().andExpect(handler().handlerType(SimpleController.class));
}
@Test
public void methodCallOnNonMock() {
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> performRequest().andExpect(handler().methodCall("bogus")))
.withMessageContaining("The supplied object [bogus] is not an instance of")
.withMessageContaining(MvcUriComponentsBuilder.MethodInvocationInfo.class.getName())
.withMessageContaining("Ensure that you invoke the handler method via MvcUriComponentsBuilder.on()");
}
@Test
public void methodCall() throws Exception {
performRequest().andExpect(handler().methodCall(on(SimpleController.class).handle()));
}
@Test
public void methodName() throws Exception {
performRequest().andExpect(handler().methodName("handle"));
}
@Test
public void methodNameMatchers() throws Exception {
performRequest()
.andExpect(handler().methodName(equalTo("handle")))
.andExpect(handler().methodName(is(not("save"))));
}
@Test
public void method() throws Exception {
Method method = SimpleController.class.getMethod("handle");
performRequest().andExpect(handler().method(method));
}
private ResultActions performRequest() {
EntityExchangeResult<Void> result = client.get().uri("/").exchange().expectBody().isEmpty();
return MockMvcTestClient.resultActionsFor(result);
}
@RestController
static class SimpleController {
@RequestMapping("/")
public ResponseEntity<Void> handle() {
return ResponseEntity.ok().build();
}
}
}

View File

@@ -0,0 +1,272 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.WebRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.fail;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.http.HttpHeaders.IF_MODIFIED_SINCE;
import static org.springframework.http.HttpHeaders.LAST_MODIFIED;
import static org.springframework.http.HttpHeaders.VARY;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.HeaderAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class HeaderAssertionTests {
private static final String ERROR_MESSAGE = "Should have thrown an AssertionError";
private String now;
private String minuteAgo;
private WebTestClient testClient;
private final long currentTime = System.currentTimeMillis();
private SimpleDateFormat dateFormat;
@BeforeEach
public void setup() {
this.dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
this.dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
this.now = dateFormat.format(new Date(this.currentTime));
this.minuteAgo = dateFormat.format(new Date(this.currentTime - (1000 * 60)));
PersonController controller = new PersonController();
controller.setStubTimestamp(this.currentTime);
this.testClient = MockMvcTestClient.bindToController(controller).build();
}
@Test
public void stringWithCorrectResponseHeaderValue() {
testClient.get().uri("/persons/1").header(IF_MODIFIED_SINCE, minuteAgo)
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals(LAST_MODIFIED, now);
}
@Test
public void stringWithMatcherAndCorrectResponseHeaderValue() {
testClient.get().uri("/persons/1").header(IF_MODIFIED_SINCE, minuteAgo)
.exchange()
.expectStatus().isOk()
.expectHeader().value(LAST_MODIFIED, equalTo(now));
}
@Test
public void multiStringHeaderValue() {
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals(VARY, "foo", "bar");
}
@Test
public void multiStringHeaderValueWithMatchers() {
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().values(VARY, hasItems(containsString("foo"), startsWith("bar")));
}
@Test
public void dateValueWithCorrectResponseHeaderValue() {
testClient.get().uri("/persons/1")
.header(IF_MODIFIED_SINCE, minuteAgo)
.exchange()
.expectStatus().isOk()
.expectHeader().valueEqualsDate(LAST_MODIFIED, this.currentTime);
}
@Test
public void longValueWithCorrectResponseHeaderValue() {
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("X-Rate-Limiting", 42);
}
@Test
public void stringWithMissingResponseHeader() {
testClient.get().uri("/persons/1")
.header(IF_MODIFIED_SINCE, now)
.exchange()
.expectStatus().isNotModified()
.expectHeader().valueEquals("X-Custom-Header");
}
@Test
public void stringWithMatcherAndMissingResponseHeader() {
testClient.get().uri("/persons/1").header(IF_MODIFIED_SINCE, now)
.exchange()
.expectStatus().isNotModified()
.expectHeader().value("X-Custom-Header", nullValue());
}
@Test
public void longValueWithMissingResponseHeader() {
try {
testClient.get().uri("/persons/1").header(IF_MODIFIED_SINCE, now)
.exchange()
.expectStatus().isNotModified()
.expectHeader().valueEquals("X-Custom-Header", 99L);
fail(ERROR_MESSAGE);
}
catch (AssertionError err) {
if (ERROR_MESSAGE.equals(err.getMessage())) {
throw err;
}
assertThat(err.getMessage()).startsWith("Response does not contain header 'X-Custom-Header'");
}
}
@Test
public void exists() {
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().exists(LAST_MODIFIED);
}
@Test
public void existsFail() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().exists("X-Custom-Header"));
}
@Test
public void doesNotExist() {
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().doesNotExist("X-Custom-Header");
}
@Test
public void doesNotExistFail() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().doesNotExist(LAST_MODIFIED));
}
@Test
public void longValueWithIncorrectResponseHeaderValue() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
testClient.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("X-Rate-Limiting", 1));
}
@Test
public void stringWithMatcherAndIncorrectResponseHeaderValue() {
long secondLater = this.currentTime + 1000;
String expected = this.dateFormat.format(new Date(secondLater));
assertIncorrectResponseHeader(spec -> spec.expectHeader().valueEquals(LAST_MODIFIED, expected), expected);
assertIncorrectResponseHeader(spec -> spec.expectHeader().value(LAST_MODIFIED, equalTo(expected)), expected);
// Comparison by date uses HttpHeaders to format the date in the error message.
HttpHeaders headers = new HttpHeaders();
headers.setDate("expected", secondLater);
assertIncorrectResponseHeader(spec -> spec.expectHeader().valueEqualsDate(LAST_MODIFIED, secondLater), expected);
}
private void assertIncorrectResponseHeader(Consumer<WebTestClient.ResponseSpec> assertions, String expected) {
try {
WebTestClient.ResponseSpec spec = testClient.get().uri("/persons/1")
.header(IF_MODIFIED_SINCE, minuteAgo)
.exchange()
.expectStatus().isOk();
assertions.accept(spec);
fail(ERROR_MESSAGE);
}
catch (AssertionError err) {
if (ERROR_MESSAGE.equals(err.getMessage())) {
throw err;
}
assertMessageContains(err, "Response header '" + LAST_MODIFIED + "'");
assertMessageContains(err, expected);
assertMessageContains(err, this.now);
}
}
private void assertMessageContains(AssertionError error, String expected) {
assertThat(error.getMessage().contains(expected))
.as("Failure message should contain [" + expected + "], actual is [" + error.getMessage() + "]")
.isTrue();
}
@Controller
private static class PersonController {
private long timestamp;
public void setStubTimestamp(long timestamp) {
this.timestamp = timestamp;
}
@RequestMapping("/persons/{id}")
public ResponseEntity<Person> showEntity(@PathVariable long id, WebRequest request) {
return ResponseEntity
.ok()
.lastModified(this.timestamp)
.header("X-Rate-Limiting", "42")
.header("Vary", "foo", "bar")
.body(new Person("Jason"));
}
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.in;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.JsonPathAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class JsonPathAssertionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new MusicController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType(MediaType.APPLICATION_JSON))
.configureClient()
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
@Test
public void exists() {
String composerByName = "$.composers[?(@.name == '%s')]";
String performerByName = "$.performers[?(@.name == '%s')]";
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath(composerByName, "Johann Sebastian Bach").exists()
.jsonPath(composerByName, "Johannes Brahms").exists()
.jsonPath(composerByName, "Edvard Grieg").exists()
.jsonPath(composerByName, "Robert Schumann").exists()
.jsonPath(performerByName, "Vladimir Ashkenazy").exists()
.jsonPath(performerByName, "Yehudi Menuhin").exists()
.jsonPath("$.composers[0]").exists()
.jsonPath("$.composers[1]").exists()
.jsonPath("$.composers[2]").exists()
.jsonPath("$.composers[3]").exists();
}
@Test
public void doesNotExist() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers[?(@.name == 'Edvard Grieeeeeeg')]").doesNotExist()
.jsonPath("$.composers[?(@.name == 'Robert Schuuuuuuman')]").doesNotExist()
.jsonPath("$.composers[4]").doesNotExist();
}
@Test
public void equality() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers[0].name").isEqualTo("Johann Sebastian Bach")
.jsonPath("$.performers[1].name").isEqualTo("Yehudi Menuhin");
// Hamcrest matchers...
client.get().uri("/music/people")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody()
.jsonPath("$.composers[0].name").value(equalTo("Johann Sebastian Bach"))
.jsonPath("$.performers[1].name").value(equalTo("Yehudi Menuhin"));
}
@Test
public void hamcrestMatcher() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers[0].name").value(startsWith("Johann"))
.jsonPath("$.performers[0].name").value(endsWith("Ashkenazy"))
.jsonPath("$.performers[1].name").value(containsString("di Me"))
.jsonPath("$.composers[1].name").value(is(in(Arrays.asList("Johann Sebastian Bach", "Johannes Brahms"))));
}
@Test
public void hamcrestMatcherWithParameterizedJsonPath() {
String composerName = "$.composers[%s].name";
String performerName = "$.performers[%s].name";
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath(composerName, 0).value(startsWith("Johann"))
.jsonPath(performerName, 0).value(endsWith("Ashkenazy"))
.jsonPath(performerName, 1).value(containsString("di Me"))
.jsonPath(composerName, 1).value(is(in(Arrays.asList("Johann Sebastian Bach", "Johannes Brahms"))));
}
@RestController
private class MusicController {
@RequestMapping("/music/people")
public MultiValueMap<String, Person> get() {
MultiValueMap<String, Person> map = new LinkedMultiValueMap<>();
map.add("composers", new Person("Johann Sebastian Bach"));
map.add("composers", new Person("Johannes Brahms"));
map.add("composers", new Person("Edvard Grieg"));
map.add("composers", new Person("Robert Schumann"));
map.add("performers", new Person("Vladimir Ashkenazy"));
map.add("performers", new Person("Yehudi Menuhin"));
return map;
}
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import javax.validation.Valid;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.ModelAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class ModelAssertionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new SampleController("a string value", 3, new Person("a name")))
.controllerAdvice(new ModelAttributeAdvice())
.alwaysExpect(status().isOk())
.build();
@Test
void attributeEqualTo() throws Exception {
performRequest(HttpMethod.GET, "/")
.andExpect(model().attribute("integer", 3))
.andExpect(model().attribute("string", "a string value"))
.andExpect(model().attribute("integer", equalTo(3))) // Hamcrest...
.andExpect(model().attribute("string", equalTo("a string value")))
.andExpect(model().attribute("globalAttrName", equalTo("Global Attribute Value")));
}
@Test
void attributeExists() throws Exception {
performRequest(HttpMethod.GET, "/")
.andExpect(model().attributeExists("integer", "string", "person"))
.andExpect(model().attribute("integer", notNullValue())) // Hamcrest...
.andExpect(model().attribute("INTEGER", nullValue()));
}
@Test
void attributeHamcrestMatchers() throws Exception {
performRequest(HttpMethod.GET, "/")
.andExpect(model().attribute("integer", equalTo(3)))
.andExpect(model().attribute("string", allOf(startsWith("a string"), endsWith("value"))))
.andExpect(model().attribute("person", hasProperty("name", equalTo("a name"))));
}
@Test
void hasErrors() throws Exception {
performRequest(HttpMethod.POST, "/persons").andExpect(model().attributeHasErrors("person"));
}
@Test
void hasNoErrors() throws Exception {
performRequest(HttpMethod.GET, "/").andExpect(model().hasNoErrors());
}
private ResultActions performRequest(HttpMethod method, String uri) {
EntityExchangeResult<Void> result = client.method(method).uri(uri).exchange().expectBody().isEmpty();
return MockMvcTestClient.resultActionsFor(result);
}
@Controller
private static class SampleController {
private final Object[] values;
SampleController(Object... values) {
this.values = values;
}
@RequestMapping("/")
String handle(Model model) {
for (Object value : this.values) {
model.addAttribute(value);
}
return "view";
}
@PostMapping("/persons")
String create(@Valid Person person, BindingResult result, Model model) {
return "view";
}
}
@ControllerAdvice
private static class ModelAttributeAdvice {
@ModelAttribute("globalAttrName")
String getAttribute() {
return "Global Attribute Value";
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.HandlerMapping;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.RequestAttributeAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class RequestAttributeAssertionTests {
private final WebTestClient mainServletClient =
MockMvcTestClient.bindToController(new SimpleController())
.defaultRequest(get("/").servletPath("/main"))
.build();
private final WebTestClient client =
MockMvcTestClient.bindToController(new SimpleController()).build();
@Test
void requestAttributeEqualTo() throws Exception {
performRequest(mainServletClient, "/main/1")
.andExpect(request().attribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/{id}"))
.andExpect(request().attribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/1"));
}
@Test
void requestAttributeMatcher() throws Exception {
String producibleMediaTypes = HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
performRequest(client, "/1")
.andExpect(request().attribute(producibleMediaTypes, hasItem(MediaType.APPLICATION_JSON)))
.andExpect(request().attribute(producibleMediaTypes, not(hasItem(MediaType.APPLICATION_XML))));
performRequest(mainServletClient, "/main/1")
.andExpect(request().attribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, equalTo("/{id}")))
.andExpect(request().attribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, equalTo("/1")));
}
private ResultActions performRequest(WebTestClient client, String uri) {
EntityExchangeResult<Void> result = client.get().uri(uri)
.exchange()
.expectStatus().isOk()
.expectBody().isEmpty();
return MockMvcTestClient.resultActionsFor(result);
}
@Controller
private static class SimpleController {
@GetMapping(path="/{id}", produces="application/json")
String show() {
return "view";
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import java.util.Locale;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.SessionAttributeAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class SessionAttributeAssertionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new SimpleController())
.alwaysExpect(status().isOk())
.build();
@Test
void sessionAttributeEqualTo() throws Exception {
performRequest().andExpect(request().sessionAttribute("locale", Locale.UK));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> performRequest().andExpect(request().sessionAttribute("locale", Locale.US)))
.withMessage("Session attribute 'locale' expected:<en_US> but was:<en_GB>");
}
@Test
void sessionAttributeMatcher() throws Exception {
performRequest()
.andExpect(request().sessionAttribute("bogus", is(nullValue())))
.andExpect(request().sessionAttribute("locale", is(notNullValue())))
.andExpect(request().sessionAttribute("locale", equalTo(Locale.UK)));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> performRequest().andExpect(request().sessionAttribute("bogus", is(notNullValue()))))
.withMessageContaining("null");
}
@Test
void sessionAttributeDoesNotExist() throws Exception {
performRequest().andExpect(request().sessionAttributeDoesNotExist("bogus", "enigma"));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> performRequest().andExpect(request().sessionAttributeDoesNotExist("locale")))
.withMessage("Session attribute 'locale' exists");
}
private ResultActions performRequest() {
EntityExchangeResult<Void> result = client.post().uri("/").exchange().expectBody().isEmpty();
return MockMvcTestClient.resultActionsFor(result);
}
@Controller
@SessionAttributes("locale")
private static class SimpleController {
@ModelAttribute
void populate(Model model) {
model.addAttribute("locale", Locale.UK);
}
@RequestMapping("/")
String handle() {
return "view";
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.StatusAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class StatusAssertionTests {
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new StatusController()).build();
@Test
public void testStatusInt() {
testClient.get().uri("/created").exchange().expectStatus().isEqualTo(201);
testClient.get().uri("/createdWithComposedAnnotation").exchange().expectStatus().isEqualTo(201);
testClient.get().uri("/badRequest").exchange().expectStatus().isEqualTo(400);
}
@Test
public void testHttpStatus() {
testClient.get().uri("/created").exchange().expectStatus().isCreated();
testClient.get().uri("/createdWithComposedAnnotation").exchange().expectStatus().isCreated();
testClient.get().uri("/badRequest").exchange().expectStatus().isBadRequest();
}
@Test
public void testMatcher() {
testClient.get().uri("/badRequest").exchange().expectStatus().value(equalTo(400));
}
@RequestMapping
@ResponseStatus
@Retention(RetentionPolicy.RUNTIME)
@interface Get {
@AliasFor(annotation = RequestMapping.class, attribute = "path")
String[] path() default {};
@AliasFor(annotation = ResponseStatus.class, attribute = "code")
HttpStatus status() default INTERNAL_SERVER_ERROR;
}
@Controller
private static class StatusController {
@RequestMapping("/created")
@ResponseStatus(CREATED)
public @ResponseBody void created(){
}
@Get(path = "/createdWithComposedAnnotation", status = CREATED)
public @ResponseBody void createdWithComposedAnnotation() {
}
@RequestMapping("/badRequest")
@ResponseStatus(code = BAD_REQUEST, reason = "Expired token")
public @ResponseBody void badRequest(){
}
@RequestMapping("/notImplemented")
@ResponseStatus(NOT_IMPLEMENTED)
public @ResponseBody void notImplemented(){
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrlPattern;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.UrlAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class UrlAssertionTests {
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new SimpleController()).build();
@Test
public void testRedirect() {
testClient.get().uri("/persons")
.exchange()
.expectStatus().isFound()
.expectHeader().location("/persons/1");
}
@Test
public void testRedirectPattern() throws Exception {
EntityExchangeResult<Void> result =
testClient.get().uri("/persons").exchange().expectBody().isEmpty();
MockMvcTestClient.resultActionsFor(result)
.andExpect(redirectedUrlPattern("/persons/*"));
}
@Test
public void testForward() {
testClient.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Forwarded-Url", "/home");
}
@Test
public void testForwardPattern() throws Exception {
EntityExchangeResult<Void> result =
testClient.get().uri("/").exchange().expectBody().isEmpty();
MockMvcTestClient.resultActionsFor(result)
.andExpect(forwardedUrlPattern("/ho?e"));
}
@Controller
private static class SimpleController {
@RequestMapping("/persons")
public String save() {
return "redirect:/persons/1";
}
@RequestMapping("/")
public String forward() {
return "forward:/home";
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import org.junit.jupiter.api.Test;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.UrlAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class ViewNameAssertionTests {
private final WebTestClient client =
MockMvcTestClient.bindToController(new SimpleController())
.alwaysExpect(status().isOk())
.build();
@Test
public void testEqualTo() throws Exception {
MockMvcTestClient.resultActionsFor(performRequest())
.andExpect(view().name("mySpecialView"))
.andExpect(view().name(equalTo("mySpecialView")));
}
@Test
public void testHamcrestMatcher() throws Exception {
MockMvcTestClient.resultActionsFor(performRequest())
.andExpect(view().name(containsString("Special")));
}
private EntityExchangeResult<Void> performRequest() {
return client.get().uri("/").exchange().expectBody().isEmpty();
}
@Controller
private static class SimpleController {
@RequestMapping("/")
public String handle() {
return "mySpecialView";
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import java.util.Arrays;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.XmlContentAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class XmlContentAssertionTests {
private static final String PEOPLE_XML =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<people><composers>" +
"<composer><name>Johann Sebastian Bach</name><someBoolean>false</someBoolean><someDouble>21.0</someDouble></composer>" +
"<composer><name>Johannes Brahms</name><someBoolean>false</someBoolean><someDouble>0.0025</someDouble></composer>" +
"<composer><name>Edvard Grieg</name><someBoolean>false</someBoolean><someDouble>1.6035</someDouble></composer>" +
"<composer><name>Robert Schumann</name><someBoolean>false</someBoolean><someDouble>NaN</someDouble></composer>" +
"</composers></people>";
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new MusicController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType(MediaType.parseMediaType("application/xml;charset=UTF-8")))
.configureClient()
.defaultHeader(HttpHeaders.ACCEPT, "application/xml;charset=UTF-8")
.build();
@Test
public void testXmlEqualTo() {
testClient.get().uri("/music/people")
.exchange()
.expectBody().xml(PEOPLE_XML);
}
@Test
public void testNodeHamcrestMatcher() {
testClient.get().uri("/music/people")
.exchange()
.expectBody().xpath("/people/composers/composer[1]").exists();
}
@Controller
private static class MusicController {
@RequestMapping(value="/music/people")
public @ResponseBody PeopleWrapper getPeople() {
List<Person> composers = Arrays.asList(
new Person("Johann Sebastian Bach").setSomeDouble(21),
new Person("Johannes Brahms").setSomeDouble(.0025),
new Person("Edvard Grieg").setSomeDouble(1.6035),
new Person("Robert Schumann").setSomeDouble(Double.NaN));
return new PeopleWrapper(composers);
}
}
@SuppressWarnings("unused")
@XmlRootElement(name="people")
@XmlAccessorType(XmlAccessType.FIELD)
private static class PeopleWrapper {
@XmlElementWrapper(name="composers")
@XmlElement(name="composer")
private List<Person> composers;
public PeopleWrapper() {
}
public PeopleWrapper(List<Person> composers) {
this.composers = composers;
}
public List<Person> getComposers() {
return this.composers;
}
}
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright 2002-2020 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
*
* https://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.servlet.samples.client.standalone.resultmatches;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.Person;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.client.MockMvcTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import static org.hamcrest.Matchers.closeTo;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.HEAD;
/**
* MockMvcTestClient equivalent of the MockMvc
* {@link org.springframework.test.web.servlet.samples.standalone.resultmatchers.XpathAssertionTests}.
*
* @author Rossen Stoyanchev
*/
public class XpathAssertionTests {
private static final Map<String, String> musicNamespace =
Collections.singletonMap("ns", "https://example.org/music/people");
private final WebTestClient testClient =
MockMvcTestClient.bindToController(new MusicController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType(MediaType.parseMediaType("application/xml;charset=UTF-8")))
.configureClient()
.defaultHeader(HttpHeaders.ACCEPT, "application/xml;charset=UTF-8")
.build();
@Test
public void testExists() {
String composer = "/ns:people/composers/composer[%s]";
String performer = "/ns:people/performers/performer[%s]";
testClient.get().uri("/music/people")
.exchange()
.expectBody()
.xpath(composer, musicNamespace, 1).exists()
.xpath(composer, musicNamespace, 2).exists()
.xpath(composer, musicNamespace, 3).exists()
.xpath(composer, musicNamespace, 4).exists()
.xpath(performer, musicNamespace, 1).exists()
.xpath(performer, musicNamespace, 2).exists()
.xpath(composer, musicNamespace, 1).string(notNullValue());
}
@Test
public void testDoesNotExist() {
String composer = "/ns:people/composers/composer[%s]";
String performer = "/ns:people/performers/performer[%s]";
testClient.get().uri("/music/people")
.exchange()
.expectBody()
.xpath(composer, musicNamespace, 0).doesNotExist()
.xpath(composer, musicNamespace, 5).doesNotExist()
.xpath(performer, musicNamespace, 0).doesNotExist()
.xpath(performer, musicNamespace, 3).doesNotExist();
}
@Test
public void testString() {
String composerName = "/ns:people/composers/composer[%s]/name";
String performerName = "/ns:people/performers/performer[%s]/name";
testClient.get().uri("/music/people")
.exchange()
.expectBody()
.xpath(composerName, musicNamespace, 1).isEqualTo("Johann Sebastian Bach")
.xpath(composerName, musicNamespace, 2).isEqualTo("Johannes Brahms")
.xpath(composerName, musicNamespace, 3).isEqualTo("Edvard Grieg")
.xpath(composerName, musicNamespace, 4).isEqualTo("Robert Schumann")
.xpath(performerName, musicNamespace, 1).isEqualTo("Vladimir Ashkenazy")
.xpath(performerName, musicNamespace, 2).isEqualTo("Yehudi Menuhin")
.xpath(composerName, musicNamespace, 1).string(equalTo("Johann Sebastian Bach")) // Hamcrest..
.xpath(composerName, musicNamespace, 1).string(startsWith("Johann"))
.xpath(composerName, musicNamespace, 1).string(notNullValue());
}
@Test
public void testNumber() {
String expression = "/ns:people/composers/composer[%s]/someDouble";
testClient.get().uri("/music/people")
.exchange()
.expectBody()
.xpath(expression, musicNamespace, 1).isEqualTo(21d)
.xpath(expression, musicNamespace, 2).isEqualTo(.0025)
.xpath(expression, musicNamespace, 3).isEqualTo(1.6035)
.xpath(expression, musicNamespace, 4).isEqualTo(Double.NaN)
.xpath(expression, musicNamespace, 1).number(equalTo(21d)) // Hamcrest..
.xpath(expression, musicNamespace, 3).number(closeTo(1.6, .01));
}
@Test
public void testBoolean() {
String expression = "/ns:people/performers/performer[%s]/someBoolean";
testClient.get().uri("/music/people")
.exchange()
.expectBody()
.xpath(expression, musicNamespace, 1).isEqualTo(false)
.xpath(expression, musicNamespace, 2).isEqualTo(true);
}
@Test
public void testNodeCount() {
testClient.get().uri("/music/people")
.exchange()
.expectBody()
.xpath("/ns:people/composers/composer", musicNamespace).nodeCount(4)
.xpath("/ns:people/performers/performer", musicNamespace).nodeCount(2)
.xpath("/ns:people/composers/composer", musicNamespace).nodeCount(equalTo(4)) // Hamcrest..
.xpath("/ns:people/performers/performer", musicNamespace).nodeCount(equalTo(2));
}
@Test
public void testFeedWithLinefeedChars() {
MockMvcTestClient.bindToController(new BlogFeedController()).build()
.get().uri("/blog.atom")
.accept(MediaType.APPLICATION_ATOM_XML)
.exchange()
.expectBody()
.xpath("//feed/title").isEqualTo("Test Feed")
.xpath("//feed/icon").isEqualTo("https://www.example.com/favicon.ico");
}
@Controller
private static class MusicController {
@RequestMapping(value = "/music/people")
public @ResponseBody
PeopleWrapper getPeople() {
List<Person> composers = Arrays.asList(
new Person("Johann Sebastian Bach").setSomeDouble(21),
new Person("Johannes Brahms").setSomeDouble(.0025),
new Person("Edvard Grieg").setSomeDouble(1.6035),
new Person("Robert Schumann").setSomeDouble(Double.NaN));
List<Person> performers = Arrays.asList(
new Person("Vladimir Ashkenazy").setSomeBoolean(false),
new Person("Yehudi Menuhin").setSomeBoolean(true));
return new PeopleWrapper(composers, performers);
}
}
@SuppressWarnings("unused")
@XmlRootElement(name = "people", namespace = "https://example.org/music/people")
@XmlAccessorType(XmlAccessType.FIELD)
private static class PeopleWrapper {
@XmlElementWrapper(name = "composers")
@XmlElement(name = "composer")
private List<Person> composers;
@XmlElementWrapper(name = "performers")
@XmlElement(name = "performer")
private List<Person> performers;
public PeopleWrapper() {
}
public PeopleWrapper(List<Person> composers, List<Person> performers) {
this.composers = composers;
this.performers = performers;
}
public List<Person> getComposers() {
return this.composers;
}
public List<Person> getPerformers() {
return this.performers;
}
}
@Controller
public class BlogFeedController {
@RequestMapping(value = "/blog.atom", method = {GET, HEAD})
@ResponseBody
public String listPublishedPosts() {
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
+ "<feed xmlns=\"http://www.w3.org/2005/Atom\">\r\n"
+ " <title>Test Feed</title>\r\n"
+ " <icon>https://www.example.com/favicon.ico</icon>\r\n"
+ "</feed>\r\n\r\n";
}
}
}

View File

@@ -29,7 +29,7 @@ public class PersonController {
private final PersonDao personDao;
PersonController(PersonDao personDao) {
public PersonController(PersonDao personDao) {
this.personDao = personDao;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -18,12 +18,12 @@ package org.springframework.test.web.servlet.samples.standalone;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -113,8 +113,6 @@ public class AsyncTests {
.andExpect(request().asyncStarted())
.andReturn();
this.asyncController.onMessage("Joe");
this.mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
@@ -151,8 +149,6 @@ public class AsyncTests {
.andExpect(request().asyncStarted())
.andReturn();
this.asyncController.onMessage("Joe");
this.mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
@@ -183,8 +179,6 @@ public class AsyncTests {
assertThat(writer.toString().contains("Async started = true")).isTrue();
writer = new StringWriter();
this.asyncController.onMessage("Joe");
this.mockMvc.perform(asyncDispatch(mvcResult))
.andDo(print(writer))
.andExpect(status().isOk())
@@ -199,10 +193,6 @@ public class AsyncTests {
@RequestMapping(path = "/{id}", produces = "application/json")
private static class AsyncController {
private final Collection<DeferredResult<Person>> deferredResults = new CopyOnWriteArrayList<>();
private final Collection<ListenableFutureTask<Person>> futureTasks = new CopyOnWriteArrayList<>();
@RequestMapping(params = "callable")
public Callable<Person> getCallable() {
return () -> new Person("Joe");
@@ -235,9 +225,9 @@ public class AsyncTests {
@RequestMapping(params = "deferredResult")
public DeferredResult<Person> getDeferredResult() {
DeferredResult<Person> deferredResult = new DeferredResult<>();
this.deferredResults.add(deferredResult);
return deferredResult;
DeferredResult<Person> result = new DeferredResult<>();
delay(100, () -> result.setResult(new Person("Joe")));
return result;
}
@RequestMapping(params = "deferredResultWithImmediateValue")
@@ -249,26 +239,15 @@ public class AsyncTests {
@RequestMapping(params = "deferredResultWithDelayedError")
public DeferredResult<Person> getDeferredResultWithDelayedError() {
final DeferredResult<Person> deferredResult = new DeferredResult<>();
new Thread() {
@Override
public void run() {
try {
Thread.sleep(100);
deferredResult.setErrorResult(new RuntimeException("Delayed Error"));
}
catch (InterruptedException e) {
/* no-op */
}
}
}.start();
return deferredResult;
DeferredResult<Person> result = new DeferredResult<>();
delay(100, () -> result.setErrorResult(new RuntimeException("Delayed Error")));
return result;
}
@RequestMapping(params = "listenableFuture")
public ListenableFuture<Person> getListenableFuture() {
ListenableFutureTask<Person> futureTask = new ListenableFutureTask<>(() -> new Person("Joe"));
this.futureTasks.add(futureTask);
delay(100, futureTask);
return futureTask;
}
@@ -281,19 +260,12 @@ public class AsyncTests {
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String errorHandler(Exception e) {
return e.getMessage();
public String errorHandler(Exception ex) {
return ex.getMessage();
}
void onMessage(String name) {
for (DeferredResult<Person> deferredResult : this.deferredResults) {
deferredResult.setResult(new Person(name));
this.deferredResults.remove(deferredResult);
}
for (ListenableFutureTask<Person> futureTask : this.futureTasks) {
futureTask.run();
this.futureTasks.remove(futureTask);
}
private void delay(long millis, Runnable task) {
Mono.delay(Duration.ofMillis(millis)).doOnTerminate(task).subscribe();
}
}

View File

@@ -42,7 +42,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
class ExceptionHandlerTests {
public class ExceptionHandlerTests {
@Nested
class MvcTests {
@@ -62,14 +62,6 @@ class ExceptionHandlerTests {
.andExpect(status().isOk())
.andExpect(forwardedUrl("globalErrorView"));
}
@Test
void globalExceptionHandlerMethodUsingClassArgument() throws Exception {
standaloneSetup(PersonController.class).setControllerAdvice(GlobalExceptionHandler.class).build()
.perform(get("/person/Bonnie"))
.andExpect(status().isOk())
.andExpect(forwardedUrl("globalErrorView"));
}
}
@@ -146,7 +138,7 @@ class ExceptionHandlerTests {
void noHandlerFound() throws Exception {
standaloneSetup(RestPersonController.class)
.setControllerAdvice(RestGlobalExceptionHandler.class, RestPersonControllerExceptionHandler.class)
.addDispatcherServletCustomizer(dispatcherServlet -> dispatcherServlet.setThrowExceptionIfNoHandlerFound(true))
.addDispatcherServletCustomizer(servlet -> servlet.setThrowExceptionIfNoHandlerFound(true))
.build()
.perform(get("/bogus").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())

View File

@@ -48,9 +48,12 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
* @author Sam Brannen
* @see org.springframework.test.web.servlet.result.PrintingResultHandlerTests
*/
@Disabled("Not intended to be executed with the build. Comment out this line to inspect the output manually.")
@Disabled
public class PrintingResultHandlerSmokeTests {
// Not intended to be executed with the build.
// Comment out class-level @Disabled to see the output.
@Test
public void testPrint() throws Exception {
StringWriter writer = new StringWriter();

View File

@@ -41,7 +41,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
class FlashAttributeAssertionTests {
public class FlashAttributeAssertionTests {
private final MockMvc mockMvc = standaloneSetup(new PersonController())
.alwaysExpect(status().isFound())

View File

@@ -49,7 +49,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
*
* @author Rossen Stoyanchev
*/
class ModelAssertionTests {
public class ModelAssertionTests {
private MockMvc mockMvc;

View File

@@ -36,7 +36,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
*
* @author Rossen Stoyanchev
*/
class RequestAttributeAssertionTests {
public class RequestAttributeAssertionTests {
private final MockMvc mockMvc = standaloneSetup(new SimpleController()).build();

View File

@@ -43,7 +43,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
class SessionAttributeAssertionTests {
public class SessionAttributeAssertionTests {
private final MockMvc mockMvc = standaloneSetup(new SimpleController())
.defaultRequest(get("/"))