Add RestTestClient

See gh-34428

Signed-off-by: Rob Worsnop <rworsnop@gmail.com>
This commit is contained in:
Rob Worsnop
2025-07-30 07:13:48 +01:00
committed by rstoyanchev
parent 336a5d0ac8
commit 37dcca54d2
35 changed files with 5742 additions and 1 deletions
@@ -55,6 +55,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
@SuppressWarnings("deprecation")
public class MockMvcClientHttpRequestFactoryTests {
@Autowired
@@ -0,0 +1,147 @@
/*
* Copyright 2002-present 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.client;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.BDDMockito.mock;
import static org.mockito.BDDMockito.when;
/**
* Tests for {@link CookieAssertions}
*
* @author Rob Worsnop
*/
public class CookieAssertionsTests {
private final ResponseCookie cookie = ResponseCookie.from("foo", "bar")
.maxAge(Duration.ofMinutes(30))
.domain("foo.com")
.path("/foo")
.secure(true)
.httpOnly(true)
.partitioned(true)
.sameSite("Lax")
.build();
private final CookieAssertions assertions = cookieAssertions(cookie);
@Test
void valueEquals() {
assertions.valueEquals("foo", "bar");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.valueEquals("what?!", "bar"));
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.valueEquals("foo", "what?!"));
}
@Test
void value() {
assertions.value("foo", equalTo("bar"));
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.value("foo", equalTo("what?!")));
}
@Test
void valueConsumer() {
assertions.value("foo", input -> assertThat(input).isEqualTo("bar"));
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.value("foo", input -> assertThat(input).isEqualTo("what?!")));
}
@Test
void exists() {
assertions.exists("foo");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.exists("what?!"));
}
@Test
void doesNotExist() {
assertions.doesNotExist("what?!");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.doesNotExist("foo"));
}
@Test
void maxAge() {
assertions.maxAge("foo", Duration.ofMinutes(30));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertions.maxAge("foo", Duration.ofMinutes(29)));
assertions.maxAge("foo", equalTo(Duration.ofMinutes(30).getSeconds()));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertions.maxAge("foo", equalTo(Duration.ofMinutes(29).getSeconds())));
}
@Test
void domain() {
assertions.domain("foo", "foo.com");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.domain("foo", "what.com"));
assertions.domain("foo", equalTo("foo.com"));
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.domain("foo", equalTo("what.com")));
}
@Test
void path() {
assertions.path("foo", "/foo");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.path("foo", "/what"));
assertions.path("foo", equalTo("/foo"));
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.path("foo", equalTo("/what")));
}
@Test
void secure() {
assertions.secure("foo", true);
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.secure("foo", false));
}
@Test
void httpOnly() {
assertions.httpOnly("foo", true);
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.httpOnly("foo", false));
}
@Test
void partitioned() {
assertions.partitioned("foo", true);
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.partitioned("foo", false));
}
@Test
void sameSite() {
assertions.sameSite("foo", "Lax");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.sameSite("foo", "Strict"));
}
private CookieAssertions cookieAssertions(ResponseCookie cookie) {
RestClient.RequestHeadersSpec.ConvertibleClientHttpResponse response = mock();
var headers = new HttpHeaders();
headers.set(HttpHeaders.SET_COOKIE, cookie.toString());
when(response.getHeaders()).thenReturn(headers);
ExchangeResult result = new ExchangeResult(response);
return new CookieAssertions(result, mock());
}
}
@@ -0,0 +1,320 @@
/*
* Copyright 2002-present 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.client;
import java.net.URI;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.springframework.http.CacheControl;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItems;
import static org.mockito.BDDMockito.mock;
import static org.mockito.BDDMockito.when;
/**
* Tests for {@link HeaderAssertions}.
*
* @author Rob Worsnop
*/
class HeaderAssertionTests {
@Test
void valueEquals() {
HttpHeaders headers = new HttpHeaders();
headers.add("foo", "bar");
headers.add("age", "22");
HeaderAssertions assertions = headerAssertions(headers);
// Success
assertions.valueEquals("foo", "bar");
assertions.value("foo", s -> assertThat(s).isEqualTo("bar"));
assertions.values("foo", strings -> assertThat(strings).containsExactly("bar"));
assertions.valueEquals("age", 22);
// Missing header
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.valueEquals("what?!", "bar"));
// Wrong value
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.valueEquals("foo", "what?!"));
// Wrong # of values
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.valueEquals("foo", "bar", "what?!"));
}
@Test
void valueEqualsWithMultipleValues() {
HttpHeaders headers = new HttpHeaders();
headers.add("foo", "bar");
headers.add("foo", "baz");
HeaderAssertions assertions = headerAssertions(headers);
// Success
assertions.valueEquals("foo", "bar", "baz");
// Wrong value
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.valueEquals("foo", "bar", "what?!"));
// Too few values
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.valueEquals("foo", "bar"));
}
@Test
void valueMatches() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/json;charset=UTF-8"));
HeaderAssertions assertions = headerAssertions(headers);
// Success
assertions.valueMatches("Content-Type", ".*UTF-8.*");
// Wrong pattern
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertions.valueMatches("Content-Type", ".*ISO-8859-1.*"))
.satisfies(ex -> assertThat(ex).hasMessage("Response header " +
"'Content-Type'=[application/json;charset=UTF-8] does not match " +
"[.*ISO-8859-1.*]"));
}
@Test
void valueMatchesWithNonexistentHeader() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/json;charset=UTF-8"));
HeaderAssertions assertions = headerAssertions(headers);
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertions.valueMatches("Content-XYZ", ".*ISO-8859-1.*"))
.withMessage("Response header 'Content-XYZ' not found");
}
@Test
void valuesMatch() {
HttpHeaders headers = new HttpHeaders();
headers.add("foo", "value1");
headers.add("foo", "value2");
headers.add("foo", "value3");
HeaderAssertions assertions = headerAssertions(headers);
assertions.valuesMatch("foo", "val.*1", "val.*2", "val.*3");
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertions.valuesMatch("foo", ".*", "val.*5"))
.satisfies(ex -> assertThat(ex).hasMessage(
"Response header 'foo' has fewer or more values [value1, value2, value3] " +
"than number of patterns to match with [.*, val.*5]"));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertions.valuesMatch("foo", ".*", "val.*5", ".*"))
.satisfies(ex -> assertThat(ex).hasMessage(
"Response header 'foo'[1]='value2' does not match 'val.*5'"));
}
@Test
void valueMatcher() {
HttpHeaders headers = new HttpHeaders();
headers.add("foo", "bar");
HeaderAssertions assertions = headerAssertions(headers);
assertions.value("foo", containsString("a"));
}
@Test
void valuesMatcher() {
HttpHeaders headers = new HttpHeaders();
headers.add("foo", "bar");
headers.add("foo", "baz");
HeaderAssertions assertions = headerAssertions(headers);
assertions.values("foo", hasItems("bar", "baz"));
}
@Test
void exists() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HeaderAssertions assertions = headerAssertions(headers);
// Success
assertions.exists("Content-Type");
// Header should not exist
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.exists("Framework"))
.satisfies(ex -> assertThat(ex).hasMessage("Response header 'Framework' does not exist"));
}
@Test
void doesNotExist() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/json;charset=UTF-8"));
HeaderAssertions assertions = headerAssertions(headers);
// Success
assertions.doesNotExist("Framework");
// Existing header
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.doesNotExist("Content-Type"))
.satisfies(ex -> assertThat(ex).hasMessage("Response header " +
"'Content-Type' exists with value=[application/json;charset=UTF-8]"));
}
@Test
void contentTypeCompatibleWith() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
HeaderAssertions assertions = headerAssertions(headers);
// Success
assertions.contentTypeCompatibleWith(MediaType.parseMediaType("application/*"));
assertions.contentTypeCompatibleWith("application/*");
// MediaTypes not compatible
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertions.contentTypeCompatibleWith(MediaType.TEXT_XML))
.withMessage("Response header 'Content-Type'=[application/xml] is not compatible with [text/xml]");
}
@Test
void location() {
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create("http://localhost:8080/"));
HeaderAssertions assertions = headerAssertions(headers);
// Success
assertions.location("http://localhost:8080/");
// Wrong value
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.location("http://localhost:8081/"));
}
@Test
void cacheControl() {
CacheControl control = CacheControl.maxAge(1, TimeUnit.HOURS).noTransform();
HttpHeaders headers = new HttpHeaders();
headers.setCacheControl(control.getHeaderValue());
HeaderAssertions assertions = headerAssertions(headers);
// Success
assertions.cacheControl(control);
// Wrong value
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.cacheControl(CacheControl.noStore()));
}
@Test
void contentDisposition() {
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("foo", "bar");
HeaderAssertions assertions = headerAssertions(headers);
assertions.contentDisposition(ContentDisposition.formData().name("foo").filename("bar").build());
// Wrong value
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.contentDisposition(ContentDisposition.attachment().build()));
}
@Test
void contentLength() {
HttpHeaders headers = new HttpHeaders();
headers.setContentLength(100);
HeaderAssertions assertions = headerAssertions(headers);
assertions.contentLength(100);
// Wrong value
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.contentLength(200));
}
@Test
void contentType() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HeaderAssertions assertions = headerAssertions(headers);
assertions.contentType(MediaType.APPLICATION_JSON);
assertions.contentType("application/json");
// Wrong value
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.contentType(MediaType.APPLICATION_XML));
}
@Test
void expires() {
HttpHeaders headers = new HttpHeaders();
ZonedDateTime expires = ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC"));
headers.setExpires(expires);
HeaderAssertions assertions = headerAssertions(headers);
assertions.expires(expires.toInstant().toEpochMilli());
// Wrong value
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.expires(expires.toInstant().toEpochMilli() + 1));
}
@Test
void lastModified() {
HttpHeaders headers = new HttpHeaders();
ZonedDateTime lastModified = ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC"));
headers.setLastModified(lastModified.toInstant().toEpochMilli());
HeaderAssertions assertions = headerAssertions(headers);
assertions.lastModified(lastModified.toInstant().toEpochMilli());
// Wrong value
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.lastModified(lastModified.toInstant().toEpochMilli() + 1));
}
@Test
void equalsDate() {
HttpHeaders headers = new HttpHeaders();
headers.setDate("foo", 1000);
HeaderAssertions assertions = headerAssertions(headers);
assertions.valueEqualsDate("foo", 1000);
// Wrong value
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.valueEqualsDate("foo", 2000));
}
private HeaderAssertions headerAssertions(HttpHeaders responseHeaders) {
RestClient.RequestHeadersSpec.ConvertibleClientHttpResponse response = mock();
when(response.getHeaders()).thenReturn(responseHeaders);
ExchangeResult result = new ExchangeResult(response);
return new HeaderAssertions(result, mock());
}
}
@@ -0,0 +1,218 @@
/*
* Copyright 2002-present 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.client;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
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.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
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;
/**
* Tests JSON Path assertions with {@link RestTestClient}.
*
* @author Rob Worsnop
*/
class JsonPathAssertionTests {
private final RestTestClient client =
RestTestClient.standaloneSetup(new MusicController())
.configureServer(builder ->
builder.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType(MediaType.APPLICATION_JSON))
)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
@Test
void exists() {
String composerByName = "$.composers[?(@.name == '%s')]";
String performerByName = "$.performers[?(@.name == '%s')]";
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath(composerByName.formatted("Johann Sebastian Bach")).exists()
.jsonPath(composerByName.formatted("Johannes Brahms")).exists()
.jsonPath(composerByName.formatted("Edvard Grieg")).exists()
.jsonPath(composerByName.formatted("Robert Schumann")).exists()
.jsonPath(performerByName.formatted("Vladimir Ashkenazy")).exists()
.jsonPath(performerByName.formatted("Yehudi Menuhin")).exists()
.jsonPath("$.composers[0]").exists()
.jsonPath("$.composers[1]").exists()
.jsonPath("$.composers[2]").exists()
.jsonPath("$.composers[3]").exists();
}
@Test
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
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
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
void hamcrestMatcherWithParameterizedJsonPath() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers[0].name").value(String.class, startsWith("Johann"))
.jsonPath("$.composers[0].name").value(String.class, s -> assertThat(s).startsWith("Johann"))
.jsonPath("$.composers[0].name").value(o -> assertThat((String) o).startsWith("Johann"))
.jsonPath("$.performers[1].name").value(containsString("di Me"))
.jsonPath("$.composers[1].name").value(is(in(Arrays.asList("Johann Sebastian Bach", "Johannes Brahms"))));
}
@Test
void isEmpty() {
client.get().uri("/music/instruments")
.exchange()
.expectBody()
.jsonPath("$.clarinets").isEmpty();
}
@Test
void isNotEmpty() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers").isNotEmpty();
}
@Test
void hasJsonPath() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers").hasJsonPath();
}
@Test
void doesNotHaveJsonPath() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.audience").doesNotHaveJsonPath();
}
@Test
void isBoolean() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers[0].someBoolean").isBoolean();
}
@Test
void isNumber() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers[0].someDouble").isNumber();
}
@Test
void isMap() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$").isMap();
}
@Test
void isArray() {
client.get().uri("/music/people")
.exchange()
.expectBody()
.jsonPath("$.composers").isArray();
}
@RestController
private static class MusicController {
@GetMapping("/music/instruments")
public Map<String, Object> getInstruments() {
return Map.of("clarinets", List.of());
}
@GetMapping("/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;
}
}
}
@@ -0,0 +1,103 @@
/*
* Copyright 2002-present 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.client;
import java.io.IOException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.client.MockMvcClientHttpRequestFactory;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Tests that use a {@link RestTestClient} configured with a
* {@link MockMvcClientHttpRequestFactory} that is in turn configured with a
* {@link MockMvc} instance that uses a standalone controller
*
* @author Rob Worsnop
*/
@ExtendWith(SpringExtension.class)
public class MockMvcClientHttpRequestFactoryTests {
private RestTestClient client;
@BeforeEach
public void setup() {
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController()).build();
this.client = RestTestClient.bindTo(mockMvc).build();
}
@Test
public void withResult() {
client.get()
.uri("/foo")
.cookie("session", "12345")
.exchange()
.expectCookie().valueEquals("session", "12345")
.expectBody(String.class)
.isEqualTo("bar");
}
@Test
public void withError() {
client.get()
.uri("/error")
.exchange()
.expectStatus().isBadRequest()
.expectBody().isEmpty();
}
@Test
public void withErrorAndBody() {
client.get().uri("/errorbody")
.exchange()
.expectStatus().isBadRequest()
.expectBody(String.class)
.isEqualTo("some really bad request");
}
@RestController
static class TestController {
@GetMapping(value = "/foo")
public void foo(@CookieValue("session") String session, HttpServletResponse response) throws IOException {
response.getWriter().write("bar");
response.addCookie(new Cookie("session", session));
}
@GetMapping(value = "/error")
public void handleError(HttpServletResponse response) throws Exception {
response.sendError(400);
}
@GetMapping(value = "/errorbody")
public void handleErrorWithBody(HttpServletResponse response) throws Exception {
response.sendError(400);
response.getWriter().write("some really bad request");
}
}
}
@@ -0,0 +1,266 @@
/*
* Copyright 2002-present 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.client;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.mockito.BDDMockito.mock;
import static org.mockito.BDDMockito.when;
/**
* Tests for {@link StatusAssertions}.
*
* @author Rob Worsnop
*/
class StatusAssertionTests {
@Test
void isEqualTo() {
StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT);
// Success
assertions.isEqualTo(HttpStatus.CONFLICT);
assertions.isEqualTo(409);
// Wrong status
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.isEqualTo(HttpStatus.REQUEST_TIMEOUT));
// Wrong status value
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.isEqualTo(408));
}
@Test
void isEqualToWithCustomStatus() {
StatusAssertions assertions = statusAssertions(600);
// Success
assertions.isEqualTo(600);
// Wrong status
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
statusAssertions(601).isEqualTo(600));
}
@Test
void reasonEquals() {
StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT);
// Success
assertions.reasonEquals("Conflict");
// Wrong reason
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).reasonEquals("Conflict"));
}
@Test
void statusSeries1xx() {
StatusAssertions assertions = statusAssertions(HttpStatus.CONTINUE);
// Success
assertions.is1xxInformational();
// Wrong series
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
statusAssertions(HttpStatus.OK).is1xxInformational());
}
@Test
void statusSeries2xx() {
StatusAssertions assertions = statusAssertions(HttpStatus.OK);
// Success
assertions.is2xxSuccessful();
// Wrong series
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).is2xxSuccessful());
}
@Test
void statusSeries3xx() {
StatusAssertions assertions = statusAssertions(HttpStatus.PERMANENT_REDIRECT);
// Success
assertions.is3xxRedirection();
// Wrong series
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).is3xxRedirection());
}
@Test
void statusSeries4xx() {
StatusAssertions assertions = statusAssertions(HttpStatus.BAD_REQUEST);
// Success
assertions.is4xxClientError();
// Wrong series
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).is4xxClientError());
}
@Test
void statusSeries5xx() {
StatusAssertions assertions = statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR);
// Success
assertions.is5xxServerError();
// Wrong series
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
statusAssertions(HttpStatus.OK).is5xxServerError());
}
@Test
void matchesStatusValue() {
StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT);
// Success
assertions.value(equalTo(409));
assertions.value(greaterThan(400));
// Wrong status
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
assertions.value(equalTo(200)));
}
@Test
void matchesCustomStatusValue() {
statusAssertions(600).value(equalTo(600));
}
@Test
void consumesStatusValue() {
StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT);
// Success
assertions.value((Integer value) -> assertThat(value).isEqualTo(409));
}
@Test
void statusIsAccepted() {
// Success
statusAssertions(HttpStatus.ACCEPTED).isAccepted();
// Wrong status
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isAccepted());
}
@Test
void statusIsNoContent() {
// Success
statusAssertions(HttpStatus.NO_CONTENT).isNoContent();
// Wrong status
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isNoContent());
}
@Test
void statusIsFound() {
// Success
statusAssertions(HttpStatus.FOUND).isFound();
// Wrong status
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isFound());
}
@Test
void statusIsSeeOther() {
// Success
statusAssertions(HttpStatus.SEE_OTHER).isSeeOther();
// Wrong status
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isSeeOther());
}
@Test
void statusIsNotModified() {
// Success
statusAssertions(HttpStatus.NOT_MODIFIED).isNotModified();
// Wrong status
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isNotModified());
}
@Test
void statusIsTemporaryRedirect() {
// Success
statusAssertions(HttpStatus.TEMPORARY_REDIRECT).isTemporaryRedirect();
// Wrong status
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isTemporaryRedirect());
}
@Test
void statusIsPermanentRedirect() {
// Success
statusAssertions(HttpStatus.PERMANENT_REDIRECT).isPermanentRedirect();
// Wrong status
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isPermanentRedirect());
}
@Test
void statusIsUnauthorized() {
// Success
statusAssertions(HttpStatus.UNAUTHORIZED).isUnauthorized();
// Wrong status
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isUnauthorized());
}
@Test
void statusIsForbidden() {
// Success
statusAssertions(HttpStatus.FORBIDDEN).isForbidden();
// Wrong status
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isForbidden());
}
private StatusAssertions statusAssertions(HttpStatus status) {
return statusAssertions(status.value());
}
private StatusAssertions statusAssertions(int status) {
try {
RestClient.RequestHeadersSpec.ConvertibleClientHttpResponse response = mock();
when(response.getStatusCode()).thenReturn(HttpStatusCode.valueOf(status));
ExchangeResult result = new ExchangeResult(response);
return new StatusAssertions(result, mock());
}
catch (IOException ex) {
throw new AssertionError(ex);
}
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2002-present 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.client.samples;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Tests with error status codes or error conditions.
*
* @author Rob Worsnop
*/
class ErrorTests {
private final RestTestClient client = RestTestClient.standaloneSetup(new TestController()).build();
@Test
void notFound(){
this.client.get().uri("/invalid")
.exchange()
.expectStatus().isNotFound();
}
@Test
void serverException() {
this.client.get().uri("/server-error")
.exchange()
.expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
}
@RestController
static class TestController {
@GetMapping("/server-error")
void handleAndThrowException() {
throw new IllegalStateException("server error");
}
}
}
@@ -0,0 +1,86 @@
/*
* Copyright 2002-present 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.client.samples;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
/**
* Tests with headers and cookies.
*
* @author Rob Worsnop
*/
class HeaderAndCookieTests {
private final RestTestClient client = RestTestClient.standaloneSetup(new TestController()).build();
@Test
void requestResponseHeaderPair() {
this.client.get().uri("/header-echo")
.header("h1", "in")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("h1", "in-out");
}
@Test
void headerMultipleValues() {
this.client.get().uri("/header-multi-value")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("h1", "v1", "v2", "v3");
}
@Test
void setCookies() {
this.client.get().uri("/cookie-echo")
.cookies(cookies -> cookies.add("k1", "v1"))
.exchange()
.expectHeader().valueMatches("Set-Cookie", "k1=v1");
}
@RestController
static class TestController {
@GetMapping("header-echo")
ResponseEntity<Void> handleHeader(@RequestHeader("h1") String myHeader) {
String value = myHeader + "-out";
return ResponseEntity.ok().header("h1", value).build();
}
@GetMapping("header-multi-value")
ResponseEntity<Void> multiValue() {
return ResponseEntity.ok().header("h1", "v1", "v2", "v3").build();
}
@GetMapping("cookie-echo")
ResponseEntity<Void> handleCookie(@CookieValue("k1") String cookieValue) {
HttpHeaders headers = new HttpHeaders();
headers.set("Set-Cookie", "k1=" + cookieValue);
return new ResponseEntity<>(headers, HttpStatus.OK);
}
}
}
@@ -0,0 +1,171 @@
/*
* Copyright 2002-present 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.client.samples;
import java.net.URI;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.json.JsonCompareMode;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.Matchers.containsString;
/**
* Samples of tests using {@link RestTestClient} with serialized JSON content.
*
* @author Rob Worsnop
*/
class JsonContentTests {
private final RestTestClient client = RestTestClient.standaloneSetup(new PersonController()).build();
@Test
void jsonContentWithDefaultLenientMode() {
this.client.get().uri("/persons")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().json("""
[
{"firstName":"Jane"},
{"firstName":"Jason"},
{"firstName":"John"}
]
""");
}
@Test
void jsonContentWithStrictMode() {
this.client.get().uri("/persons")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().json("""
[
{"firstName":"Jane", "lastName":"Williams"},
{"firstName":"Jason","lastName":"Johnson"},
{"firstName":"John", "lastName":"Smith"}
]
""",
JsonCompareMode.STRICT);
}
@Test
void jsonContentWithStrictModeAndMissingAttributes() {
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> this.client.get().uri("/persons")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectBody().json("""
[
{"firstName":"Jane"},
{"firstName":"Jason"},
{"firstName":"John"}
]
""",
JsonCompareMode.STRICT)
);
}
@Test
void jsonPathIsEqualTo() {
this.client.get().uri("/persons")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].firstName").isEqualTo("Jane")
.jsonPath("$[1].firstName").isEqualTo("Jason")
.jsonPath("$[2].firstName").isEqualTo("John");
}
@Test
void jsonPathMatches() {
this.client.get().uri("/persons/John/Smith")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.firstName").value(containsString("oh"));
}
@Test
void postJsonContent() {
this.client.post().uri("/persons")
.contentType(MediaType.APPLICATION_JSON)
.body("""
{"firstName":"John", "lastName":"Smith"}
""")
.exchange()
.expectStatus().isCreated()
.expectBody().isEmpty();
}
@RestController
@RequestMapping("/persons")
static class PersonController {
@GetMapping
List<Person> getPersons() {
return List.of(new Person("Jane", "Williams"), new Person("Jason", "Johnson"), new Person("John", "Smith"));
}
@GetMapping("/{firstName}/{lastName}")
Person getPerson(@PathVariable String firstName, @PathVariable String lastName) {
return new Person(firstName, lastName);
}
@PostMapping
ResponseEntity<String> savePerson(@RequestBody Person person) {
return ResponseEntity.created(URI.create(String.format("/persons/%s/%s", person.getFirstName(), person.getLastName()))).build();
}
}
static class Person {
private String firstName;
private String lastName;
public Person() {
}
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2002-present 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.client.samples;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.xml.bind.annotation.XmlRootElement;
import org.jspecify.annotations.Nullable;
@XmlRootElement
class Person {
private String name;
// No-arg constructor for XML
public Person() {
}
@JsonCreator
public Person(@JsonProperty("name") String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
Person person = (Person) other;
return getName().equals(person.getName());
}
@Override
public int hashCode() {
return getName().hashCode();
}
@Override
public String toString() {
return "Person[name='" + name + "']";
}
}
@@ -0,0 +1,156 @@
/*
* Copyright 2002-present 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.client.samples;
import java.net.URI;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.startsWith;
/**
* Annotated controllers accepting and returning typed Objects.
*
* @author Rob Worsnop
*/
class ResponseEntityTests {
private final RestTestClient client = RestTestClient.standaloneSetup(new PersonController())
.baseUrl("/persons")
.build();
@Test
void entity() {
this.client.get().uri("/John")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody(Person.class).isEqualTo(new Person("John"));
}
@Test
void entityMatcher() {
this.client.get().uri("/John")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody(Person.class).value(Person::getName, startsWith("Joh"));
}
@Test
void entityWithConsumer() {
this.client.get().uri("/John")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody(Person.class)
.consumeWith(result -> assertThat(result.getResponseBody()).isEqualTo(new Person("John")));
}
@Test
void entityList() {
List<Person> expected = List.of(
new Person("Jane"), new Person("Jason"), new Person("John"));
this.client.get()
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody(new ParameterizedTypeReference<List<Person>>() {}).isEqualTo(expected);
}
@Test
void entityListWithConsumer() {
this.client.get()
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody(new ParameterizedTypeReference<List<Person>>() {})
.value(people ->
assertThat(people).contains(new Person("Jason"))
);
}
@Test
void entityMap() {
Map<String, Person> map = new LinkedHashMap<>();
map.put("Jane", new Person("Jane"));
map.put("Jason", new Person("Jason"));
map.put("John", new Person("John"));
this.client.get().uri("?map=true")
.exchange()
.expectStatus().isOk()
.expectBody(new ParameterizedTypeReference<Map<String, Person>>() {}).isEqualTo(map);
}
@Test
void postEntity() {
this.client.post()
.contentType(MediaType.APPLICATION_JSON)
.body(new Person("John"))
.exchange()
.expectStatus().isCreated()
.expectHeader().valueEquals("location", "/persons/John")
.expectBody().isEmpty();
}
@RestController
@RequestMapping("/persons")
static class PersonController {
@GetMapping(path = "/{name}", produces = MediaType.APPLICATION_JSON_VALUE)
Person getPerson(@PathVariable String name) {
return new Person(name);
}
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
List<Person> getPersons() {
return List.of(new Person("Jane"), new Person("Jason"), new Person("John"));
}
@GetMapping(params = "map", produces = MediaType.APPLICATION_JSON_VALUE)
Map<String, Person> getPersonsAsMap() {
Map<String, Person> map = new LinkedHashMap<>();
map.put("Jane", new Person("Jane"));
map.put("Jason", new Person("Jason"));
map.put("John", new Person("John"));
return map;
}
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<String> savePerson(@RequestBody Person person) {
return ResponseEntity.created(URI.create("/persons/" + person.getName())).build();
}
}
}
@@ -0,0 +1,330 @@
/*
* Copyright 2002-present 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.client.samples;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Map;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.DefaultUriBuilderFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests using the {@link RestTestClient} API.
*/
class RestTestClientTests {
private RestTestClient client;
@BeforeEach
void setUp() {
this.client = RestTestClient.standaloneSetup(new TestController()).build();
}
@Nested
class HttpMethods {
@ParameterizedTest
@ValueSource(strings = {"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"})
void testMethod(String method) {
RestTestClientTests.this.client.method(HttpMethod.valueOf(method)).uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.method").isEqualTo(method);
}
@Test
void testGet() {
RestTestClientTests.this.client.get().uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.method").isEqualTo("GET");
}
@Test
void testPost() {
RestTestClientTests.this.client.post().uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.method").isEqualTo("POST");
}
@Test
void testPut() {
RestTestClientTests.this.client.put().uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.method").isEqualTo("PUT");
}
@Test
void testDelete() {
RestTestClientTests.this.client.delete().uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.method").isEqualTo("DELETE");
}
@Test
void testPatch() {
RestTestClientTests.this.client.patch().uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.method").isEqualTo("PATCH");
}
@Test
void testHead() {
RestTestClientTests.this.client.head().uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.method").isEqualTo("HEAD");
}
@Test
void testOptions() {
RestTestClientTests.this.client.options().uri("/test")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Allow", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS")
.expectBody().isEmpty();
}
}
@Nested
class Mutation {
@Test
void test() {
RestTestClientTests.this.client.mutate()
.apply(builder -> builder.defaultHeader("foo", "bar"))
.uriBuilderFactory(new DefaultUriBuilderFactory("/test"))
.defaultCookie("foo", "bar")
.defaultCookies(cookies -> cookies.add("a", "b"))
.defaultHeaders(headers -> headers.set("a", "b"))
.build().get()
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.uri").isEqualTo("/test")
.jsonPath("$.headers.Cookie").isEqualTo("foo=bar; a=b")
.jsonPath("$.headers.foo").isEqualTo("bar")
.jsonPath("$.headers.a").isEqualTo("b");
}
}
@Nested
class Uris {
@Test
void test() {
RestTestClientTests.this.client.get().uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.uri").isEqualTo("/test");
}
@Test
void testWithPathVariables() {
RestTestClientTests.this.client.get().uri("/test/{id}", 1)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.uri").isEqualTo("/test/1");
}
@Test
void testWithParameterMap() {
RestTestClientTests.this.client.get().uri("/test/{id}", Map.of("id", 1))
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.uri").isEqualTo("/test/1");
}
@Test
void testWithUrlBuilder() {
RestTestClientTests.this.client.get().uri(builder -> builder.path("/test/{id}").build(1))
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.uri").isEqualTo("/test/1");
}
@Test
void testURI() {
RestTestClientTests.this.client.get().uri(URI.create("/test"))
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.uri").isEqualTo("/test");
}
}
@Nested
class Cookies {
@Test
void testCookie() {
RestTestClientTests.this.client.get().uri("/test")
.cookie("foo", "bar")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.headers.Cookie").isEqualTo("foo=bar");
}
@Test
void testCookies() {
RestTestClientTests.this.client.get().uri("/test")
.cookies(cookies -> cookies.add("foo", "bar"))
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.headers.Cookie").isEqualTo("foo=bar");
}
}
@Nested
class Headers {
@Test
void testHeader() {
RestTestClientTests.this.client.get().uri("/test")
.header("foo", "bar")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.headers.foo").isEqualTo("bar");
}
@Test
void testHeaders() {
RestTestClientTests.this.client.get().uri("/test")
.headers(headers -> headers.set("foo", "bar"))
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.headers.foo").isEqualTo("bar");
}
@Test
void testContentType() {
RestTestClientTests.this.client.post().uri("/test")
.contentType(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.headers.Content-Type").isEqualTo("application/json");
}
@Test
void testAcceptCharset() {
RestTestClientTests.this.client.get().uri("/test")
.acceptCharset(StandardCharsets.UTF_8)
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.headers.Accept-Charset").isEqualTo("utf-8");
}
@Test
void testIfModifiedSince() {
RestTestClientTests.this.client.get().uri("/test")
.ifModifiedSince(ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneId.of("GMT")))
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.headers.If-Modified-Since").isEqualTo("Thu, 01 Jan 1970 00:00:00 GMT");
}
@Test
void testIfNoneMatch() {
RestTestClientTests.this.client.get().uri("/test")
.ifNoneMatch("foo")
.exchange()
.expectStatus().isOk()
.expectBody().jsonPath("$.headers.If-None-Match").isEqualTo("foo");
}
}
@Nested
class Expectations {
@Test
void testExpectCookie() {
RestTestClientTests.this.client.get().uri("/test")
.exchange()
.expectCookie().value("session", Matchers.equalTo("abc"));
}
}
@Nested
class ReturnResults {
@Test
void testBodyReturnResult() {
var result = RestTestClientTests.this.client.get().uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody(Map.class).returnResult();
assertThat(result.getResponseBody().get("uri")).isEqualTo("/test");
}
@Test
void testReturnResultClass() {
var result = RestTestClientTests.this.client.get().uri("/test")
.exchange()
.expectStatus().isOk()
.returnResult(Map.class);
assertThat(result.getResponseBody().get("uri")).isEqualTo("/test");
}
@Test
void testReturnResultParameterizedTypeReference() {
var result = RestTestClientTests.this.client.get().uri("/test")
.exchange()
.expectStatus().isOk()
.returnResult(new ParameterizedTypeReference<Map<String, Object>>() {
});
assertThat(result.getResponseBody().get("uri")).isEqualTo("/test");
}
}
@RestController
static class TestController {
@RequestMapping(path = {"/test", "/test/*"}, produces = "application/json")
public Map<String, Object> handle(
@RequestHeader HttpHeaders headers,
HttpServletRequest request, HttpServletResponse response) {
response.addCookie(new Cookie("session", "abc"));
return Map.of(
"method", request.getMethod(),
"uri", request.getRequestURI(),
"headers", headers.toSingleValueMap()
);
}
}
}
@@ -0,0 +1,72 @@
/*
* Copyright 2002-present 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.client.samples;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Integration tests for {@link RestTestClient} with soft assertions.
*
*/
class SoftAssertionTests {
private final RestTestClient restTestClient = RestTestClient.standaloneSetup(new TestController()).build();
@Test
void expectAll() {
this.restTestClient.get().uri("/test").exchange()
.expectAll(
responseSpec -> responseSpec.expectStatus().isOk(),
responseSpec -> responseSpec.expectBody(String.class).isEqualTo("hello")
);
}
@Test
void expectAllWithMultipleFailures() {
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() ->
this.restTestClient.get().uri("/test").exchange()
.expectAll(
responseSpec -> responseSpec.expectStatus().isBadRequest(),
responseSpec -> responseSpec.expectStatus().isOk(),
responseSpec -> responseSpec.expectBody(String.class).isEqualTo("bogus")
)
)
.withMessage("""
Multiple Exceptions (2):
Status expected:<400 BAD_REQUEST> but was:<200 OK>
Response body expected:<bogus> but was:<hello>""");
}
@RestController
static class TestController {
@GetMapping("/test")
String handle() {
return "hello";
}
}
}
@@ -0,0 +1,196 @@
/*
* Copyright 2002-present 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.client.samples;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
/**
* Samples of tests using {@link RestTestClient} with XML content.
*
* @author Rob Worsnop
*/
class XmlContentTests {
private static final String persons_XML = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persons>
<person><name>Jane</name></person>
<person><name>Jason</name></person>
<person><name>John</name></person>
</persons>
""";
private final RestTestClient client = RestTestClient.standaloneSetup(new PersonController()).build();
@Test
void xmlContent() {
this.client.get().uri("/persons")
.accept(MediaType.APPLICATION_XML)
.exchange()
.expectStatus().isOk()
.expectBody().xml(persons_XML);
}
@Test
void xpathIsEqualTo() {
this.client.get().uri("/persons")
.accept(MediaType.APPLICATION_XML)
.exchange()
.expectStatus().isOk()
.expectBody()
.xpath("/").exists()
.xpath("/persons").exists()
.xpath("/persons/person").exists()
.xpath("/persons/person").nodeCount(3)
.xpath("/persons/person[1]/name").isEqualTo("Jane")
.xpath("/persons/person[2]/name").isEqualTo("Jason")
.xpath("/persons/person[3]/name").isEqualTo("John");
}
@Test
void xpathDoesNotExist() {
this.client.get().uri("/persons")
.accept(MediaType.APPLICATION_XML)
.exchange()
.expectStatus().isOk()
.expectBody()
.xpath("/persons/person[4]").doesNotExist();
}
@Test
void xpathNodeCount() {
this.client.get().uri("/persons")
.accept(MediaType.APPLICATION_XML)
.exchange()
.expectStatus().isOk()
.expectBody()
.xpath("/persons/person").nodeCount(3)
.xpath("/persons/person").nodeCount(equalTo(3));
}
@Test
void xpathMatches() {
this.client.get().uri("/persons")
.accept(MediaType.APPLICATION_XML)
.exchange()
.expectStatus().isOk()
.expectBody()
.xpath("//person/name").string(startsWith("J"))
.xpath("//person/name").string(s -> {
if (!s.startsWith("J")) {
throw new AssertionError("Name does not start with J: " + s);
}
});
}
@Test
void xpathContainsSubstringViaRegex() {
this.client.get().uri("/persons/John")
.accept(MediaType.APPLICATION_XML)
.exchange()
.expectStatus().isOk()
.expectBody()
.xpath("//name[contains(text(), 'oh')]").exists();
}
@Test
void postXmlContent() {
String content =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<person><name>John</name></person>";
this.client.post().uri("/persons")
.contentType(MediaType.APPLICATION_XML)
.body(content)
.exchange()
.expectStatus().isCreated()
.expectHeader().valueEquals(HttpHeaders.LOCATION, "/persons/John")
.expectBody().isEmpty();
}
@SuppressWarnings("unused")
@XmlRootElement(name="persons")
@XmlAccessorType(XmlAccessType.FIELD)
private static class PersonsWrapper {
@XmlElement(name="person")
private final List<Person> persons = new ArrayList<>();
public PersonsWrapper() {
}
public PersonsWrapper(List<Person> persons) {
this.persons.addAll(persons);
}
public PersonsWrapper(Person... persons) {
this.persons.addAll(Arrays.asList(persons));
}
public List<Person> getpersons() {
return this.persons;
}
}
@RestController
@RequestMapping("/persons")
static class PersonController {
@GetMapping(produces = MediaType.APPLICATION_XML_VALUE)
PersonsWrapper getPersons() {
return new PersonsWrapper(new Person("Jane"), new Person("Jason"), new Person("John"));
}
@GetMapping(path = "/{name}", produces = MediaType.APPLICATION_XML_VALUE)
Person getPerson(@PathVariable String name) {
return new Person(name);
}
@PostMapping(consumes = MediaType.APPLICATION_XML_VALUE)
ResponseEntity<Object> savepersons(@RequestBody Person person) {
URI location = URI.create(String.format("/persons/%s", person.getName()));
return ResponseEntity.created(location).build();
}
}
}
@@ -0,0 +1,79 @@
/*
* Copyright 2002-present 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.client.samples.bind;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
/**
* Sample tests demonstrating "mock" server tests binding to server infrastructure
* declared in a Spring ApplicationContext.
*
* @author Rob Worsnop
*/
@SpringJUnitWebConfig(ApplicationContextTests.WebConfig.class)
class ApplicationContextTests {
private RestTestClient client;
private final WebApplicationContext context;
public ApplicationContextTests(WebApplicationContext context) {
this.context = context;
}
@BeforeEach
void setUp() {
this.client = RestTestClient.bindToApplicationContext(context).build();
}
@Test
void test() {
this.client.get().uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("It works!");
}
@Configuration
static class WebConfig {
@Bean
public TestController controller() {
return new TestController();
}
}
@RestController
static class TestController {
@GetMapping("/test")
public String handle() {
return "It works!";
}
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 2002-present 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.client.samples.bind;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Sample tests demonstrating "mock" server tests binding to an annotated
* controller.
*
* @author Rob Worsnop
*/
class ControllerTests {
private RestTestClient client;
@BeforeEach
void setUp() {
this.client = RestTestClient.standaloneSetup(new TestController()).build();
}
@Test
void test() {
this.client.get().uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("It works!");
}
@RestController
static class TestController {
@GetMapping("/test")
public String handle() {
return "It works!";
}
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2002-present 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.client.samples.bind;
import java.io.IOException;
import java.util.Optional;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.servlet.function.ServerResponse;
import static org.springframework.http.HttpStatus.I_AM_A_TEAPOT;
/**
* Tests for a {@link Filter}.
* @author Rob Worsnop
*/
class FilterTests {
@Test
void filter() {
Filter filter = new HttpFilter() {
@Override
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
res.getWriter().write("It works!");
}
};
RestTestClient client = RestTestClient.bindToRouterFunction(
request -> Optional.of(req -> ServerResponse.status(I_AM_A_TEAPOT).build()))
.configureServer(builder -> builder.addFilters(filter))
.build();
client.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("It works!");
}
}
@@ -0,0 +1,73 @@
/*
* Copyright 2002-present 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.client.samples.bind;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.testfixture.http.server.reactive.bootstrap.ReactorHttpServer;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
/**
* Sample tests demonstrating live server integration tests.
*
* @author Rob Worsnop
*/
class HttpServerTests {
private ReactorHttpServer server;
private RestTestClient client;
@BeforeEach
void start() throws Exception {
HttpHandler httpHandler = RouterFunctions.toHttpHandler(
route(GET("/test"), request -> ServerResponse.ok().bodyValue("It works!")));
this.server = new ReactorHttpServer();
this.server.setHandler(httpHandler);
this.server.afterPropertiesSet();
this.server.start();
this.client = RestTestClient.bindToServer()
.baseUrl("http://localhost:" + this.server.getPort())
.build();
}
@AfterEach
void stop() {
this.server.stop();
}
@Test
void test() {
this.client.get().uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("It works!");
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2002-present 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.client.samples.bind;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;
import static org.springframework.web.servlet.function.RequestPredicates.GET;
import static org.springframework.web.servlet.function.RouterFunctions.route;
/**
* Sample tests demonstrating "mock" server tests binding to a RouterFunction.
*
* @author Rob Worsnop
*/
class RouterFunctionTests {
private RestTestClient testClient;
@BeforeEach
void setUp() throws Exception {
RouterFunction<?> route = route(GET("/test"), request ->
ServerResponse.ok().body("It works!"));
this.testClient = RestTestClient.bindToRouterFunction(route).build();
}
@Test
void test() throws Exception {
this.testClient.get().uri("/test")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("It works!");
}
}