在测试方法中,接收 org.springframework.web.reactive.function.client.ClientRequest
的实例。
我想验证它的 HttpMethod、URI 和正文。
很明显如何获取除 body 之外的所有内容。
ClientRequest request = makeInstance(...);
assertEquals(HttpMethod.POST, request.method());
assertEquals("somewhere/else", request.url().toString());
// ? unclear how to extract body using the BodyInserter
BodyInserter<?, ? super ClientHttpRequest> inserter = request.body();
inserter.insert(%outputMessage%, %context%);
我在Spring的来源中找到了how BodyInserters are tested 。如何创建 BodyInserter.Context
(第二个参数)或多或少很清楚,但我不明白如何构造第一个参数,因此可以通过它提取请求正文。
请展示一种从 ClientRequest
实例获取请求正文的传统(或至少可用)方法。
最佳答案
对于这样一个简单的情况来说有点复杂,但我花了 5 个类来从 ClientRequest
中提取正文。
这似乎太多了,我仍然很好奇是否有一个简短的解决方案来解决这个问题。欢迎您对此问题提出另一个答案,以便我接受。
不幸的是,必须说的是 ClientRequest
、BodyInserters
的设计以及来自 org.springframework.web.reactive 的大多数其他内容。** *
还有很大的改进空间。目前它只是一堆接口(interface),每个接口(interface)都有大量方法,并且通常需要花费大量精力来测试代码,具体取决于这些类。
让这个方法发挥作用的主要目标:
static <T> T extractBody(ClientRequest request, Class<T> clazz) {
InsertionReceiver<T> receiver = InsertionReceiver.forClass(clazz);
return receiver.receiveValue(request.body());
}
这是InsertionReceiver
的实现:
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.web.reactive.function.BodyInserter;
public interface InsertionReceiver<T> {
T receiveValue(BodyInserter<?, ? extends ReactiveHttpOutputMessage> bodyInserter);
static <T> InsertionReceiver<T> forClass(Class<T> clazz) {
return new SimpleValueReceiver<>(clazz);
}
}
<小时/>
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.web.reactive.function.BodyInserter;
class SimpleValueReceiver<T> implements InsertionReceiver<T> {
private static final Object DUMMY = new Object();
private final Class<T> clazz;
private final AtomicReference<Object> reference;
SimpleValueReceiver(Class<T> clazz) {
this.clazz = clazz;
this.reference = new AtomicReference<>(DUMMY);
}
@Override
public T receiveValue(BodyInserter<?, ? extends ReactiveHttpOutputMessage> bodyInserter) {
demandValueFrom(bodyInserter);
return receivedValue();
}
private void demandValueFrom(BodyInserter<?, ? extends ReactiveHttpOutputMessage> bodyInserter) {
var inserter = (BodyInserter<?, ReactiveHttpOutputMessage>) bodyInserter;
inserter.insert(
MinimalHttpOutputMessage.INSTANCE,
new SingleWriterContext(new WriteToConsumer<>(reference::set))
);
}
private T receivedValue() {
Object value = reference.get();
reference.set(DUMMY);
T validatedValue;
if (value == DUMMY) {
throw new RuntimeException("Value was not received, Check your inserter worked properly");
} else if (!clazz.isAssignableFrom(value.getClass())) {
throw new RuntimeException(
"Value has unexpected type ("
+ value.getClass().getTypeName()
+ ") instead of (" + clazz.getTypeName() + ")");
} else {
validatedValue = clazz.cast(value);
}
return validatedValue;
}
}
<小时/>
class WriteToConsumer<T> implements HttpMessageWriter<T> {
private final Consumer<T> consumer;
private final List<MediaType> mediaTypes;
WriteToConsumer(Consumer<T> consumer) {
this.consumer = consumer;
this.mediaTypes = Collections.singletonList(MediaType.ALL);
}
@Override
public List<MediaType> getWritableMediaTypes() {
return mediaTypes;
}
@Override
public boolean canWrite(ResolvableType elementType, MediaType mediaType) {
return true;
}
@Override
public Mono<Void> write(
Publisher<? extends T> inputStream,
ResolvableType elementType,
MediaType mediaType,
ReactiveHttpOutputMessage message,
Map<String, Object> hints
) {
inputStream.subscribe(new OneValueConsumption<>(consumer));
return Mono.empty();
}
}
<小时/>
class MinimalHttpOutputMessage implements ReactiveHttpOutputMessage {
public static MinimalHttpOutputMessage INSTANCE = new MinimalHttpOutputMessage();
private MinimalHttpOutputMessage() {
}
@Override
public HttpHeaders getHeaders() {
return HttpHeaders.EMPTY;
}
// other overridden methods are omitted as they do nothing,
// i.e. return null, false, or have empty bodies
}
<小时/>
class OneValueConsumption<T> implements Subscriber<T> {
private final Consumer<T> consumer;
private int remainedAccepts;
public OneValueConsumption(Consumer<T> consumer) {
this.consumer = Objects.requireNonNull(consumer);
this.remainedAccepts = 1;
}
@Override
public void onSubscribe(Subscription s) {
s.request(1);
}
@Override
public void onNext(T o) {
if (remainedAccepts > 0) {
consumer.accept(o);
remainedAccepts -= 1;
} else {
throw new RuntimeException("No more values can be consumed");
}
}
@Override
public void onError(Throwable t) {
throw new RuntimeException("Single value was not consumed", t);
}
@Override
public void onComplete() {
// nothing
}
}
<小时/>
class SingleWriterContext implements BodyInserter.Context {
private final List<HttpMessageWriter<?>> singleWriterList;
SingleWriterContext(HttpMessageWriter<?> writer) {
this.singleWriterList = List.of(writer);
}
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return singleWriterList;
}
@Override
public Optional<ServerHttpRequest> serverRequest() {
return Optional.empty();
}
@Override
public Map<String, Object> hints() {
return null;
}
}
关于java - 热衷于从 spring 响应式(Reactive) ClientRequest 获取字符串形式的正文?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61156827/