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:
Stéphane Nicoll
2024-01-16 16:04:04 +01:00
parent 9f8038963f
commit e73bbd4ad3
10 changed files with 481 additions and 58 deletions

View File

@@ -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) {}
}

View File

@@ -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);
}
}

View File

@@ -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) {}
}

View File

@@ -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"))));
}