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:
parent
9f8038963f
commit
e73bbd4ad3
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
|
@ -30,6 +30,8 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import com.jayway.jsonpath.spi.mapper.MappingProvider;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.reactivestreams.Publisher;
|
||||
|
@ -57,6 +59,7 @@ import org.springframework.web.reactive.function.BodyInserters;
|
|||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFunction;
|
||||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
import org.springframework.web.util.UriBuilder;
|
||||
import org.springframework.web.util.UriBuilderFactory;
|
||||
|
||||
|
@ -72,6 +75,9 @@ class DefaultWebTestClient implements WebTestClient {
|
|||
|
||||
private final WiretapConnector wiretapConnector;
|
||||
|
||||
@Nullable
|
||||
private final JsonEncoderDecoder jsonEncoderDecoder;
|
||||
|
||||
private final ExchangeFunction exchangeFunction;
|
||||
|
||||
private final UriBuilderFactory uriBuilderFactory;
|
||||
|
@ -91,13 +97,15 @@ class DefaultWebTestClient implements WebTestClient {
|
|||
private final AtomicLong requestIndex = new AtomicLong();
|
||||
|
||||
|
||||
DefaultWebTestClient(ClientHttpConnector connector,
|
||||
DefaultWebTestClient(ClientHttpConnector connector, ExchangeStrategies exchangeStrategies,
|
||||
Function<ClientHttpConnector, ExchangeFunction> exchangeFactory, UriBuilderFactory uriBuilderFactory,
|
||||
@Nullable HttpHeaders headers, @Nullable MultiValueMap<String, String> cookies,
|
||||
Consumer<EntityExchangeResult<?>> entityResultConsumer,
|
||||
@Nullable Duration responseTimeout, DefaultWebTestClientBuilder clientBuilder) {
|
||||
|
||||
this.wiretapConnector = new WiretapConnector(connector);
|
||||
this.jsonEncoderDecoder = JsonEncoderDecoder.from(
|
||||
exchangeStrategies.messageWriters(), exchangeStrategies.messageReaders());
|
||||
this.exchangeFunction = exchangeFactory.apply(this.wiretapConnector);
|
||||
this.uriBuilderFactory = uriBuilderFactory;
|
||||
this.defaultHeaders = headers;
|
||||
|
@ -362,6 +370,7 @@ class DefaultWebTestClient implements WebTestClient {
|
|||
this.requestId, this.uriTemplate, getResponseTimeout());
|
||||
|
||||
return new DefaultResponseSpec(result, response,
|
||||
DefaultWebTestClient.this.jsonEncoderDecoder,
|
||||
DefaultWebTestClient.this.entityResultConsumer, getResponseTimeout());
|
||||
}
|
||||
|
||||
|
@ -399,6 +408,9 @@ class DefaultWebTestClient implements WebTestClient {
|
|||
|
||||
private final ClientResponse response;
|
||||
|
||||
@Nullable
|
||||
private final JsonEncoderDecoder jsonEncoderDecoder;
|
||||
|
||||
private final Consumer<EntityExchangeResult<?>> entityResultConsumer;
|
||||
|
||||
private final Duration timeout;
|
||||
|
@ -406,11 +418,13 @@ class DefaultWebTestClient implements WebTestClient {
|
|||
|
||||
DefaultResponseSpec(
|
||||
ExchangeResult exchangeResult, ClientResponse response,
|
||||
@Nullable JsonEncoderDecoder jsonEncoderDecoder,
|
||||
Consumer<EntityExchangeResult<?>> entityResultConsumer,
|
||||
Duration timeout) {
|
||||
|
||||
this.exchangeResult = exchangeResult;
|
||||
this.response = response;
|
||||
this.jsonEncoderDecoder = jsonEncoderDecoder;
|
||||
this.entityResultConsumer = entityResultConsumer;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
@ -466,7 +480,7 @@ class DefaultWebTestClient implements WebTestClient {
|
|||
ByteArrayResource resource = this.response.bodyToMono(ByteArrayResource.class).block(this.timeout);
|
||||
byte[] body = (resource != null ? resource.getByteArray() : null);
|
||||
EntityExchangeResult<byte[]> entityResult = initEntityExchangeResult(body);
|
||||
return new DefaultBodyContentSpec(entityResult);
|
||||
return new DefaultBodyContentSpec(entityResult, this.jsonEncoderDecoder);
|
||||
}
|
||||
|
||||
private <B> EntityExchangeResult<B> initEntityExchangeResult(@Nullable B body) {
|
||||
|
@ -625,10 +639,14 @@ class DefaultWebTestClient implements WebTestClient {
|
|||
|
||||
private final EntityExchangeResult<byte[]> result;
|
||||
|
||||
@Nullable
|
||||
private final JsonEncoderDecoder jsonEncoderDecoder;
|
||||
|
||||
private final boolean isEmpty;
|
||||
|
||||
DefaultBodyContentSpec(EntityExchangeResult<byte[]> result) {
|
||||
DefaultBodyContentSpec(EntityExchangeResult<byte[]> result, @Nullable JsonEncoderDecoder jsonEncoderDecoder) {
|
||||
this.result = result;
|
||||
this.jsonEncoderDecoder = jsonEncoderDecoder;
|
||||
this.isEmpty = (result.getResponseBody() == null || result.getResponseBody().length == 0);
|
||||
}
|
||||
|
||||
|
@ -666,8 +684,16 @@ class DefaultWebTestClient implements WebTestClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
public JsonPathAssertions jsonPath(String expression) {
|
||||
return new JsonPathAssertions(this, getBodyAsString(), expression,
|
||||
JsonPathConfigurationProvider.getConfiguration(this.jsonEncoderDecoder));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("removal")
|
||||
public JsonPathAssertions jsonPath(String expression, Object... args) {
|
||||
return new JsonPathAssertions(this, getBodyAsString(), expression, args);
|
||||
Assert.hasText(expression, "expression must not be null or empty");
|
||||
return jsonPath(expression.formatted(args));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -697,4 +723,18 @@ class DefaultWebTestClient implements WebTestClient {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private static class JsonPathConfigurationProvider {
|
||||
|
||||
static Configuration getConfiguration(@Nullable JsonEncoderDecoder jsonEncoderDecoder) {
|
||||
Configuration jsonPathConfiguration = Configuration.defaultConfiguration();
|
||||
if (jsonEncoderDecoder != null) {
|
||||
MappingProvider mappingProvider = new EncoderDecoderMappingProvider(
|
||||
jsonEncoderDecoder.encoder(), jsonEncoderDecoder.decoder());
|
||||
return jsonPathConfiguration.mappingProvider(mappingProvider);
|
||||
}
|
||||
return jsonPathConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
|
@ -294,8 +294,9 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
|
|||
if (connectorToUse == null) {
|
||||
connectorToUse = initConnector();
|
||||
}
|
||||
ExchangeStrategies exchangeStrategies = initExchangeStrategies();
|
||||
Function<ClientHttpConnector, ExchangeFunction> exchangeFactory = connector -> {
|
||||
ExchangeFunction exchange = ExchangeFunctions.create(connector, initExchangeStrategies());
|
||||
ExchangeFunction exchange = ExchangeFunctions.create(connector, exchangeStrategies);
|
||||
if (CollectionUtils.isEmpty(this.filters)) {
|
||||
return exchange;
|
||||
}
|
||||
|
@ -305,7 +306,7 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
|
|||
.orElse(exchange);
|
||||
|
||||
};
|
||||
return new DefaultWebTestClient(connectorToUse, exchangeFactory, initUriBuilderFactory(),
|
||||
return new DefaultWebTestClient(connectorToUse, exchangeStrategies, exchangeFactory, initUriBuilderFactory(),
|
||||
this.defaultHeaders != null ? HttpHeaders.readOnlyHttpHeaders(this.defaultHeaders) : null,
|
||||
this.defaultCookies != null ? CollectionUtils.unmodifiableMultiValueMap(this.defaultCookies) : null,
|
||||
this.entityResultConsumer, this.responseTimeout, new DefaultWebTestClientBuilder(this));
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import com.jayway.jsonpath.TypeRef;
|
||||
import com.jayway.jsonpath.spi.mapper.MappingProvider;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* JSON Path {@link MappingProvider} implementation using {@link Encoder}
|
||||
* and {@link Decoder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.2
|
||||
*/
|
||||
final class EncoderDecoderMappingProvider implements MappingProvider {
|
||||
|
||||
private final Encoder<?> encoder;
|
||||
|
||||
private final Decoder<?> decoder;
|
||||
|
||||
/**
|
||||
* Create an instance with the specified writers and readers.
|
||||
*/
|
||||
public EncoderDecoderMappingProvider(Encoder<?> encoder, Decoder<?> decoder) {
|
||||
this.encoder = encoder;
|
||||
this.decoder = decoder;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T map(Object source, Class<T> targetType, Configuration configuration) {
|
||||
return mapToTargetType(source, ResolvableType.forClass(targetType));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T map(Object source, TypeRef<T> targetType, Configuration configuration) {
|
||||
return mapToTargetType(source, ResolvableType.forType(targetType.getType()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private <T> T mapToTargetType(Object source, ResolvableType targetType) {
|
||||
DataBufferFactory bufferFactory = DefaultDataBufferFactory.sharedInstance;
|
||||
MimeType mimeType = MimeTypeUtils.APPLICATION_JSON;
|
||||
Map<String, Object> hints = Collections.emptyMap();
|
||||
|
||||
DataBuffer buffer = ((Encoder<T>) this.encoder).encodeValue(
|
||||
(T) source, bufferFactory, ResolvableType.forInstance(source), mimeType, hints);
|
||||
|
||||
return ((Decoder<T>) this.decoder).decode(buffer, targetType, mimeType, hints);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.http.MediaType;
|
||||
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.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link Encoder} and {@link Decoder} that is able to handle a map to and from
|
||||
* json. Used to configure the jsonpath infrastructure without having a hard
|
||||
* dependency on the library.
|
||||
*
|
||||
* @param encoder the json encoder
|
||||
* @param decoder the json decoder
|
||||
* @author Stephane Nicoll
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.2
|
||||
*/
|
||||
record JsonEncoderDecoder(Encoder<?> encoder, Decoder<?> decoder) {
|
||||
|
||||
private static final ResolvableType MAP_TYPE = ResolvableType.forClass(Map.class);
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link JsonEncoderDecoder} instance based on the specified
|
||||
* infrastructure.
|
||||
* @param messageWriters the HTTP message writers
|
||||
* @param messageReaders the HTTP message readers
|
||||
* @return a {@link JsonEncoderDecoder} or {@code null} if a suitable codec
|
||||
* is not available
|
||||
*/
|
||||
@Nullable
|
||||
static JsonEncoderDecoder from(Collection<HttpMessageWriter<?>> messageWriters,
|
||||
Collection<HttpMessageReader<?>> messageReaders) {
|
||||
|
||||
Encoder<?> jsonEncoder = findJsonEncoder(messageWriters);
|
||||
Decoder<?> jsonDecoder = findJsonDecoder(messageReaders);
|
||||
if (jsonEncoder != null && jsonDecoder != null) {
|
||||
return new JsonEncoderDecoder(jsonEncoder, jsonDecoder);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the first suitable {@link Encoder} that can encode a {@link Map}
|
||||
* to json.
|
||||
* @param writers the writers to inspect
|
||||
* @return a suitable json {@link Encoder} or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
private static Encoder<?> findJsonEncoder(Collection<HttpMessageWriter<?>> writers) {
|
||||
return findJsonEncoder(writers.stream()
|
||||
.filter(writer -> writer instanceof EncoderHttpMessageWriter)
|
||||
.map(writer -> ((EncoderHttpMessageWriter<?>) writer).getEncoder()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Encoder<?> findJsonEncoder(Stream<Encoder<?>> stream) {
|
||||
return stream
|
||||
.filter(encoder -> encoder.canEncode(MAP_TYPE, MediaType.APPLICATION_JSON))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first suitable {@link Decoder} that can decode a {@link Map} to
|
||||
* json.
|
||||
* @param readers the readers to inspect
|
||||
* @return a suitable json {@link Decoder} or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
private static Decoder<?> findJsonDecoder(Collection<HttpMessageReader<?>> readers) {
|
||||
return findJsonDecoder(readers.stream()
|
||||
.filter(reader -> reader instanceof DecoderHttpMessageReader)
|
||||
.map(reader -> ((DecoderHttpMessageReader<?>) reader).getDecoder()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Decoder<?> findJsonDecoder(Stream<Decoder<?>> decoderStream) {
|
||||
return decoderStream
|
||||
.filter(decoder -> decoder.canDecode(MAP_TYPE, MediaType.APPLICATION_JSON))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
}
|
|
@ -18,8 +18,10 @@ package org.springframework.test.web.reactive.server;
|
|||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.util.JsonPathExpectationsHelper;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -28,6 +30,7 @@ import org.springframework.util.Assert;
|
|||
* <a href="https://github.com/jayway/JsonPath">JsonPath</a> assertions.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Stephane Nicoll
|
||||
* @since 5.0
|
||||
* @see <a href="https://github.com/jayway/JsonPath">https://github.com/jayway/JsonPath</a>
|
||||
* @see JsonPathExpectationsHelper
|
||||
|
@ -41,11 +44,12 @@ public class JsonPathAssertions {
|
|||
private final JsonPathExpectationsHelper pathHelper;
|
||||
|
||||
|
||||
JsonPathAssertions(WebTestClient.BodyContentSpec spec, String content, String expression, Object... args) {
|
||||
JsonPathAssertions(WebTestClient.BodyContentSpec spec, String content, String expression,
|
||||
@Nullable Configuration configuration) {
|
||||
Assert.hasText(expression, "expression must not be null or empty");
|
||||
this.bodySpec = spec;
|
||||
this.content = content;
|
||||
this.pathHelper = new JsonPathExpectationsHelper(expression.formatted(args));
|
||||
this.pathHelper = new JsonPathExpectationsHelper(expression, configuration);
|
||||
}
|
||||
|
||||
|
||||
|
@ -168,6 +172,15 @@ public class JsonPathAssertions {
|
|||
return this.bodySpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link JsonPathExpectationsHelper#assertValue(String, Matcher, ParameterizedTypeReference)}.
|
||||
* @since 6.2
|
||||
*/
|
||||
public <T> WebTestClient.BodyContentSpec value(ParameterizedTypeReference<T> targetType, Matcher<? super T> matcher) {
|
||||
this.pathHelper.assertValue(this.content, matcher, targetType);
|
||||
return this.bodySpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the result of the JSONPath evaluation.
|
||||
* @since 5.1
|
||||
|
@ -199,6 +212,16 @@ public class JsonPathAssertions {
|
|||
return value(targetType, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the result of the JSONPath evaluation and provide a parameterized type.
|
||||
* @since 6.2
|
||||
*/
|
||||
public <T> WebTestClient.BodyContentSpec value(ParameterizedTypeReference<T> targetType, Consumer<T> consumer) {
|
||||
T value = this.pathHelper.evaluateJsonPath(this.content, targetType);
|
||||
consumer.accept(value);
|
||||
return this.bodySpec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
throw new AssertionError("Object#equals is disabled " +
|
||||
|
|
|
@ -1035,6 +1035,15 @@ public interface WebTestClient {
|
|||
*/
|
||||
BodyContentSpec xml(String expectedXml);
|
||||
|
||||
/**
|
||||
* Access to response body assertions using a
|
||||
* <a href="https://github.com/jayway/JsonPath">JsonPath</a> expression
|
||||
* to inspect a specific subset of the body.
|
||||
* @param expression the JsonPath expression
|
||||
* @since 6.2
|
||||
*/
|
||||
JsonPathAssertions jsonPath(String expression);
|
||||
|
||||
/**
|
||||
* Access to response body assertions using a
|
||||
* <a href="https://github.com/jayway/JsonPath">JsonPath</a> expression
|
||||
|
@ -1043,7 +1052,9 @@ public interface WebTestClient {
|
|||
* formatting specifiers as defined in {@link String#format}.
|
||||
* @param expression the JsonPath expression
|
||||
* @param args arguments to parameterize the expression
|
||||
* @deprecated in favor of calling {@link String#formatted(Object...)} upfront
|
||||
*/
|
||||
@Deprecated(since = "6.2", forRemoval = true)
|
||||
JsonPathAssertions jsonPath(String expression, Object... args);
|
||||
|
||||
/**
|
||||
|
|
|
@ -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"))));
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue