[英]Spring Webflux Mockito - mock the response of a Webclient call
[英]Spring webflux webclient make another call while evaluating response from first call
我正在使用 webclient 调用端点,并希望 map 得到另一个 object 的响应。 在映射 object 时,我想对响应的某些参数进行额外调用。
我的第一个电话返回以下 object
{
"type": "Collection",
"key": "some.key",
"settings": [
{
"type": "Struct",
"key": "steps",
"value": [
{
"type": "Struct",
"key": "1",
"value": [
{
"type": "String",
"key": "headline",
"value": "someheadline"
},
{
"type": "String",
"key": "subheadline",
"value": "somesubheadline"
},
{
"type": "Link",
"key": "link.to.another.object",
"value": {
"linkType": "Boilerplate",
"key": "configurabletextkey"
}
}
]
}
]
},
{
"type": "Struct",
"key": "commons",
"value": [
{
"type": "String",
"key": "mandatory.fields.text",
"value": "Pflichtfelder"
}
]
}
]
}
我的 map 响应如下:
webClient
.get()
.uri(
uriBuilder ->
uriBuilder
.path(headlessConfig.getEndpoint())
.pathSegment(contentId)
.build())
.retrieve()
.bodyToMono(Collection.class)
.map(response -> {
return getCollectionContent(response);
})
在 getCollectionContent 方法中,我遍历设置数组并从响应中提取数据,并将 map 数据发送到我的 PageContent Object。
public class PageContent {
private String pageId;
private List<Message> messages;
}
public class Message {
@NonNull private String key;
@NonNull private String text;
private Boolean containsHtml = false;
}
如果响应包含“字符串”类型,我只需将数据添加到消息 Object 并将其添加到 PageContent 列表中。
现在来解决问题。 如果类型是“链接”,我想像上面一样使用 webclient 对同一端点进行另一个调用,以获取该 object 的密钥和文本,创建一条消息 Object 并将其添加到我现有的列表中。
对应的代码如下所示:
webClient
.get()
.uri(
uriBuilder ->
uriBuilder
.path(headlessConfig.getEndpoint())
.pathSegment(contentKey)
.build())
.retrieve()
.bodyToMono(ConfigurableText.class)
.map(
configurableTextResponse -> {
messages.add(
new Message(
prefix + configurableTextResponse.getKey(),
configurableTextResponse.getText(),
true));
return Mono.empty();
})
现在,当我尝试这样做时,什么也没有发生,我只收到没有链接消息的 PageContent object。
以resttemplate的阻塞方式,这个逻辑应该可以工作,但我想让它与webclient一起工作。
编辑:
遍历列表并提取消息数据的代码:
private PageContent getCollectionContent(Collection response) {
PageContent pageContent = new PageContent();
pageContent.setPageId(response.getKey());
List<Message> messages = new ArrayList<>();
response
.getSettings()
.forEach(
settingsItemsArray -> {
var settingsItemList = (List<?>) settingsItemsArray.getValue();
String prefix = settingsItemsArray.getKey() + ".";
extractMessageText(prefix, (LinkedHashMap<?, ?>) settingsItemList.get(0), messages);
});
pageContent.setMessages(messages);
return pageContent;
}
用于提取 MessageText、进一步迭代或获取链接类型的缺失文本的代码。
private void extractMessageText(
String prefix, LinkedHashMap<?, ?> settingsItem, List<Message> messages) {
String itemKey = (String) settingsItem.get(KEY);
String itemType = (String) settingsItem.get(TYPE);
switch (itemType) {
case "String":
messages.add(new Message(prefix + itemKey, (String) settingsItem.get(VALUE)));
break;
case "Struct":
((List<?>) settingsItem.get(VALUE))
.forEach(
structItems ->
extractMessageText(
prefix + settingsItem.get(KEY) + ".",
(LinkedHashMap<?, ?>) structItems,
messages));
break;
case "Link":
webClient
.get()
.uri(
uriBuilder ->
uriBuilder
.path(headlessConfig.getEndpoint())
.pathSegment(contentKey)
.build())
.retrieve()
.bodyToMono(ConfigurableText.class)
.map(
configurableTextResponse -> {
messages.add(
new Message(
prefix + configurableTextResponse.getKey(),
configurableTextResponse.getText(),
true));
return Mono.empty();
})
break;
default:
break;
}
}
我已经更改了您的一些代码,以使其与反应器模式更兼容。 我已将递归更改为expandDeep
并使用 Jackson 来解析 JSON。 我希望这会给你一些想法来解决你的问题。
List<Message> messages = Flux
.fromIterable(jsonNode.get("settings"))
//expand the graph into a stream of flat data and track the address of the node with 'prefix'
//expand/exapndDeep operators are alternatives of recursion in project reactor
.expandDeep(parent -> {
String parentPrefix = Optional.ofNullable(parent.get("prefix")).map(JsonNode::asText)
.orElse(parent.get("key").asText());
String type = parent.get("type").asText();
if (type.equals("Struct")) {
return Flux.fromIterable(parent.get("value"))
.cast(ObjectNode.class)
.map(child -> child.put("prefix", parentPrefix + ":" + child.get("key").asText()));
}
return Mono.empty();
})
//we have to choose only leaf nodes aka String and Link nodes
.filter(node -> Arrays.asList("String", "Link").contains(node.get("type").asText()))
//now process expanded leaf nodes
.flatMap(leaf -> {
if ("String".equals(leaf.get("type").asText())) {
return Mono.just(new Message(leaf.get("prefix").asText(), leaf.get("value").asText(), true));
}
if ("Link".equals(leaf.get("type").asText())) {
return webClient
.get()
.uri(
uriBuilder ->
uriBuilder
.pathSegment(leaf.get("key").asText())
.build())
.retrieve()
.bodyToMono(JsonNode.class)
.map(configurableTextResponse -> new Message(
leaf.get("prefix") + configurableTextResponse.get("key").asText(),
configurableTextResponse.get("text").asText(),
true));
}
return Mono.empty();
})
// at this point we are getting stream of the Message objects from the Link/String nodes
//collect them into a list
.collectList()
//we have to subscribe()/block() the mono to actually invoke the pipline.
.block();
您的代码什么也没做的主要原因是您没有订阅 WebClient 管道。
编辑:
改变
.map(response -> {
return getCollectionContent(response);
})
至
.flatMap(response -> {
return getCollectionContent(response);
})
并从getCollectionContent(response)
Mono<PageContent> page
返回
就像是:
// at this point we are getting stream of the Message objects from the Link/String nodes
//collect them into a list
.collectList()
.map(messages -> {
PageContent pageContent = new PageContent();
pageContent.setPageId(response.get("pageId").asText());
pageContent.setMessages(messages);
return pageContent;
});
在这些更改之后,您的getCollectionContent()
将返回一个发布者Mono<PageContent>
,该发布者将从 flatMap 运算符订阅。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.