Configure jsonpath MappingProvider in WebTestClient
This commit improves jsonpath support in WebTestClient by detecting a suitable json encoder/decoder that can be applied to assert more complex data structure. Closes gh-31653
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.reactive.server;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import com.jayway.jsonpath.TypeRef;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link EncoderDecoderMappingProvider}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class EncoderDecoderMappingProviderTests {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private final EncoderDecoderMappingProvider mappingProvider = new EncoderDecoderMappingProvider(
|
||||
new Jackson2JsonEncoder(objectMapper), new Jackson2JsonDecoder(objectMapper));
|
||||
|
||||
|
||||
@Test
|
||||
void mapType() {
|
||||
Data data = this.mappingProvider.map(jsonData("test", 42), Data.class, Configuration.defaultConfiguration());
|
||||
assertThat(data).isEqualTo(new Data("test", 42));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapGenericType() {
|
||||
List<?> jsonData = List.of(jsonData("first", 1), jsonData("second", 2), jsonData("third", 3));
|
||||
List<Data> data = this.mappingProvider.map(jsonData, new TypeRef<List<Data>>() {}, Configuration.defaultConfiguration());
|
||||
assertThat(data).containsExactly(new Data("first", 1), new Data("second", 2), new Data("third", 3));
|
||||
}
|
||||
|
||||
private Map<String, Object> jsonData(String name, int counter) {
|
||||
return Map.of("name", name, "counter", counter);
|
||||
}
|
||||
|
||||
|
||||
record Data(String name, int counter) {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.reactive.server;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.codec.DecoderHttpMessageReader;
|
||||
import org.springframework.http.codec.EncoderHttpMessageWriter;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.codec.ResourceHttpMessageReader;
|
||||
import org.springframework.http.codec.ResourceHttpMessageWriter;
|
||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JsonEncoderDecoder}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class JsonEncoderDecoderTests {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private static final HttpMessageWriter<?> jacksonMessageWriter = new EncoderHttpMessageWriter<>(
|
||||
new Jackson2JsonEncoder(objectMapper));
|
||||
|
||||
private static final HttpMessageReader<?> jacksonMessageReader = new DecoderHttpMessageReader<>(
|
||||
new Jackson2JsonDecoder(objectMapper));
|
||||
|
||||
@Test
|
||||
void fromWithEmptyWriters() {
|
||||
assertThat(JsonEncoderDecoder.from(List.of(), List.of(jacksonMessageReader))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromWithEmptyReaders() {
|
||||
assertThat(JsonEncoderDecoder.from(List.of(jacksonMessageWriter), List.of())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromWithSuitableWriterAndNoReader() {
|
||||
assertThat(JsonEncoderDecoder.from(List.of(jacksonMessageWriter), List.of(new ResourceHttpMessageReader()))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromWithSuitableReaderAndNoWriter() {
|
||||
assertThat(JsonEncoderDecoder.from(List.of(new ResourceHttpMessageWriter()), List.of(jacksonMessageReader))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromWithNoSuitableReaderAndWriter() {
|
||||
JsonEncoderDecoder jsonEncoderDecoder = JsonEncoderDecoder.from(
|
||||
List.of(new ResourceHttpMessageWriter(), jacksonMessageWriter),
|
||||
List.of(new ResourceHttpMessageReader(), jacksonMessageReader));
|
||||
assertThat(jsonEncoderDecoder).isNotNull();
|
||||
assertThat(jsonEncoderDecoder.encoder()).isInstanceOf(Jackson2JsonEncoder.class);
|
||||
assertThat(jsonEncoderDecoder.decoder()).isInstanceOf(Jackson2JsonDecoder.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,15 +16,21 @@
|
||||
|
||||
package org.springframework.test.web.servlet.samples.client.standalone;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient.BodyContentSpec;
|
||||
import org.springframework.test.web.servlet.client.MockMvcWebTestClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
/**
|
||||
@@ -32,60 +38,64 @@ import static org.hamcrest.Matchers.equalTo;
|
||||
* {@link org.springframework.test.web.servlet.samples.standalone.ResponseBodyTests}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ResponseBodyTests {
|
||||
|
||||
@Test
|
||||
void json() {
|
||||
MockMvcWebTestClient.bindToController(new PersonController()).build()
|
||||
execute("/persons/Lee", body -> body.jsonPath("$.name").isEqualTo("Lee")
|
||||
.jsonPath("$.age").isEqualTo(42)
|
||||
.jsonPath("$.age").value(equalTo(42))
|
||||
.jsonPath("$.age").value(Float.class, equalTo(42.0f)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonPathWithCustomType() {
|
||||
execute("/persons/Lee", body -> body.jsonPath("$").isEqualTo(new Person("Lee", 42)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonPathWithResolvedValue() {
|
||||
execute("/persons/Lee", body -> body.jsonPath("$").value(Person.class,
|
||||
candidate -> assertThat(candidate).isEqualTo(new Person("Lee", 42))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonPathWithResolvedGenericValue() {
|
||||
execute("/persons", body -> body.jsonPath("$").value(new ParameterizedTypeReference<List<Person>>() {},
|
||||
candidate -> assertThat(candidate).hasSize(3).extracting(Person::name)
|
||||
.containsExactly("Rossen", "Juergen", "Arjen")));
|
||||
}
|
||||
|
||||
private void execute(String uri, Consumer<BodyContentSpec> assertions) {
|
||||
assertions.accept(MockMvcWebTestClient.bindToController(new PersonController()).build()
|
||||
.get()
|
||||
.uri("/person/Lee")
|
||||
.uri(uri)
|
||||
.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(Float.class, equalTo(42.0f));
|
||||
.expectBody());
|
||||
}
|
||||
|
||||
|
||||
@RestController
|
||||
@SuppressWarnings("unused")
|
||||
private static class PersonController {
|
||||
|
||||
@GetMapping("/person/{name}")
|
||||
@GetMapping("/persons")
|
||||
List<Person> getAll() {
|
||||
return List.of(new Person("Rossen", 42), new Person("Juergen", 42),
|
||||
new Person("Arjen", 42));
|
||||
}
|
||||
|
||||
@GetMapping("/persons/{name}")
|
||||
Person get(@PathVariable String name) {
|
||||
Person person = new Person(name);
|
||||
person.setAge(42);
|
||||
return person;
|
||||
return new Person(name, 42);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
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;
|
||||
}
|
||||
}
|
||||
private record Person(@NotNull String name, int age) {}
|
||||
|
||||
}
|
||||
|
||||
@@ -64,12 +64,12 @@ public class JsonPathAssertionTests {
|
||||
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(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()
|
||||
@@ -117,16 +117,13 @@ public class JsonPathAssertionTests {
|
||||
|
||||
@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"))));
|
||||
.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"))));
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user