Java 带有合并单元测试的Spring反应式WebClient不会终止
我正在试验反应式Spring和WebClient,特别是尝试同时调用不同的客户端,并最终合并结果。也许事先分享很重要,可以预期其中一个服务并不总是返回值 所以我有一个简单的服务:Java 带有合并单元测试的Spring反应式WebClient不会终止,java,spring-boot,junit,reactive-programming,spring-webflux,Java,Spring Boot,Junit,Reactive Programming,Spring Webflux,我正在试验反应式Spring和WebClient,特别是尝试同时调用不同的客户端,并最终合并结果。也许事先分享很重要,可以预期其中一个服务并不总是返回值 所以我有一个简单的服务: @Service @Slf4j public class PersonSearchService { public static final String PERSON_SERVICE = "/persons"; public static final String FELLOW_
@Service
@Slf4j
public class PersonSearchService {
public static final String PERSON_SERVICE = "/persons";
public static final String FELLOW_SERVICE = "/fellows";
private final WebClient webClient;
public PersonSearchService(@Value("${base.url}") String baseUrl) {
this.webClient = WebClient.builder()
.baseUrl(baseUrl)
.build();
}
// This method is called from a controller where a SearchCriteria is being passed - Not important
public Flux<PersonResult> getPeople(SearchCriteria search) {
return Flux.merge(
getPeople(PERSON_SERVICE, search),
getPeople(FELLOW_SERVICE, search));
}
private Flux<PersonResult> getPeople(String path, SearchCriteria search) {
return webClient.get()
.uri(path, search)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::isError, ErrorHandler::handleError)
.bodyToFlux(PersonResult.class);
}
}
因此,这正如预期的那样工作,但当我尝试对其进行单元测试时,我的测试从未终止(连接保持活动状态)。这是我的测试:
@ExtendWith(MockitoExtension.class)
class PersonSearchServiceTest {
private ObjectMapper objectMapper;
private MockWebServer mockWebServer;
private PersonSearchServiceTest personSearchService;
@BeforeEach
void setUp() {
objectMapper = new ObjectMapper();
mockWebServer = new MockWebServer();
personSearchService = new PersonSearchServiceTest(mockWebServer.url("localhost").toString());
}
@AfterEach
void tearDown() throws IOException {
mockWebServer.shutdown();
}
@Test
void getPeople_empty() {
mockWebServer.enqueue(new MockResponse()
.setResponseCode(200)
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.setBody("[]"));
SearchCriteria search = SearchCriteria.builder().build();
Flux<PersonResult> resultFlux = personSearchService.getPeople(search);
StepVerifier.create(resultFlux)
.expectNextCount(0)
.verifyComplete();
//
// assertThat(this.personSearchService.getPeople(search).collectList().block())
// .isEqualTo(Collections.emptyList());
}
@Test
void getDataPlanParcels() throws Exception {
var person = PersonResult.builder()
.id("1234")
.age(20)
.build();
mockWebServer.enqueue(new MockResponse()
.setResponseCode(200)
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.setBody(objectMapper.writeValueAsString(person)));
SearchCriteria search = Search.builder().build();
Flux<PersonResult> resultFlux = personSearchService.getPeople(search);
StepVerifier.create(resultFlux)
.expectNextMatches(result -> result.getId().equals("1234"))
.verifyComplete();
}
}
@ExtendWith(MockitoExtension.class)
班级人员搜索服务测试{
私有对象映射器对象映射器;
私有MockWebServer MockWebServer;
私人搜索服务测试人员搜索服务;
@之前
无效设置(){
objectMapper=新的objectMapper();
mockWebServer=新建mockWebServer();
personSearchService=newPersonSearchServiceTest(mockWebServer.url(“localhost”).toString());
}
@之后
void tearDown()引发IOException{
mockWebServer.shutdown();
}
@试验
void getPeople_empty(){
mockWebServer.enqueue(新的MockResponse()
.setResponseCode(200)
.setHeader(HttpHeaders.CONTENT\u TYPE、MediaType.APPLICATION\u JSON)
.立位体(“[]”);
SearchCriteria search=SearchCriteria.builder().build();
Flux resultFlux=personSearchService.getPeople(搜索);
StepVerifier.create(resultFlux)
.expectNextCount(0)
.verifyComplete();
//
//assertThat(this.personSearchService.getPeople(search.collectList().block())
//.isEqualTo(Collections.emptyList());
}
@试验
void getDataPlanParcels()引发异常{
var person=PersonResult.builder()
.id(“1234”)
.年龄(20岁)
.build();
mockWebServer.enqueue(新的MockResponse()
.setResponseCode(200)
.setHeader(HttpHeaders.CONTENT\u TYPE、MediaType.APPLICATION\u JSON)
.setBody(objectMapper.writeValueAsString(person));
SearchCriteria search=search.builder().build();
Flux resultFlux=personSearchService.getPeople(搜索);
StepVerifier.create(resultFlux)
.expectNextMatches(结果->结果.getId().equals(“1234”))
.verifyComplete();
}
}
上述两个测试中的任何一个都不会终止并等待,这是我在终端中看到的:
00:07:09.209 [reactor-http-epoll-3] DEBUG r.n.http.client.HttpClientOperations.debug(244) - [id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281] Received last HTTP packet <no request,no span>
00:07:09.218 [reactor-http-epoll-3] DEBUG r.n.r.DefaultPooledConnectionProvider.debug(249) - [id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281] onStateChange(GET{uri=/localhost/license-service/v3/ACPMDE/parcels/search, connection=PooledConnection{channel=[id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281]}}, [response_completed]) <no request,no span>
00:07:09.220 [reactor-http-epoll-3] DEBUG r.n.r.DefaultPooledConnectionProvider.debug(249) - [id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281] onStateChange(GET{uri=/localhost/license-service/v3/ACPMDE/parcels/search, connection=PooledConnection{channel=[id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281]}}, [disconnecting]) <no request,no span>
00:07:09.220 [reactor-http-epoll-3] DEBUG r.n.r.DefaultPooledConnectionProvider.debug(244) - [id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281] Releasing channel <no request,no span>
00:07:09.224 [reactor-http-epoll-3] DEBUG r.n.r.DefaultPooledConnectionProvider.debug(249) - [id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281] Channel cleaned, now 1 active connections and 1 inactive connections <no request,no span>
00:07:09.209[reactor-http-epoll-3]调试r.n.http.client.HttpClientOperations.DEBUG(244)-[id:0xee38099d,L:/127.0.0.1:49742-r:localhost/127.0.0.1:43281]收到最后一个http数据包
00:07:09.218[reactor-http-epoll-3]调试r.n.r.DefaultPooledConnectionProvider.DEBUG(249)-[id:0xee38099d,L:/127.0.0.1:49742-r:localhost/127.0.0.1:43281]onStateChange(获取{uri=/localhost/license service/v3/ACPMDE/parcels/search,connection=PooledConnection{通道=[id:0xee38099d,L:/127.0.0.0.1:49742-r:localhost/127.0.0.1:431},[答复已完成])
00:07:09.220[reactor-http-epoll-3]调试r.n.r.DefaultPooledConnectionProvider.DEBUG(249)-[id:0xee38099d,L:/127.0.0.1:49742-r:localhost/127.0.0.1:43281]onStateChange(获取{uri=/localhost/license service/v3/ACPMDE/parcels/search,connection=PooledConnection{channel=[id:0xee38099d,L:/127.0.0.1:49742-r:localhost/127.0.0.0.1:43281},[断开连接])
00:07:09.220[reactor-http-epoll-3]调试r.n.r.DefaultPooledConnectionProvider.DEBUG(244)-[id:0xee38099d,L:/127.0.0.1:49742-r:localhost/127.0.0.1:43281]释放通道
00:07:09.224[reactor-http-epoll-3]调试r.n.r.DefaultPooledConnectionProvider.DEBUG(249)-[id:0xee38099d,L:/127.0.0.1:49742-r:localhost/127.0.0.1:43281]通道已清理,现在有1个活动连接和1个非活动连接
我的测试设置有问题吗?还是需要在WewbClient上进行额外的配置?
如果我删除了
Flux.merge
或者只有一个源,那么测试是绿色的。在我看来,您只在模拟服务器上设置了一个响应。因此,第一个调用进行得很顺利,然后第二个调用进行,服务器正在等待来自模拟服务器的响应。因此,如果您等待的时间足够长,它可能会超时。因此,可能排队代码>两个响应。是的,我想测试只有一个响应时会发生什么。也就是说,在“真实”中也可能发生这种情况life-这些服务中只有一个可以提供基于搜索
的响应,而另一个没有匹配项。理想情况下,如果搜索
调用没有匹配项,您仍然会收到一个响应,指示搜索
api没有找到传入的搜索条件的响应。如果要模拟无响应,请nse那么你应该在你的web客户端上设置一个读取超时,并提供一个合适的onError
操作符来处理该故障。是的,即使找不到任何响应,这两个服务也会提供响应。我使用Wiremock服务器而不是okhttp Mock web服务器,我没有相同的问题。因此,这必须是与我的测试设置相关,但我不想对每个服务都显式地执行enqueue
。
00:07:09.209 [reactor-http-epoll-3] DEBUG r.n.http.client.HttpClientOperations.debug(244) - [id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281] Received last HTTP packet <no request,no span>
00:07:09.218 [reactor-http-epoll-3] DEBUG r.n.r.DefaultPooledConnectionProvider.debug(249) - [id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281] onStateChange(GET{uri=/localhost/license-service/v3/ACPMDE/parcels/search, connection=PooledConnection{channel=[id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281]}}, [response_completed]) <no request,no span>
00:07:09.220 [reactor-http-epoll-3] DEBUG r.n.r.DefaultPooledConnectionProvider.debug(249) - [id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281] onStateChange(GET{uri=/localhost/license-service/v3/ACPMDE/parcels/search, connection=PooledConnection{channel=[id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281]}}, [disconnecting]) <no request,no span>
00:07:09.220 [reactor-http-epoll-3] DEBUG r.n.r.DefaultPooledConnectionProvider.debug(244) - [id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281] Releasing channel <no request,no span>
00:07:09.224 [reactor-http-epoll-3] DEBUG r.n.r.DefaultPooledConnectionProvider.debug(249) - [id: 0xee38099d, L:/127.0.0.1:49742 - R:localhost/127.0.0.1:43281] Channel cleaned, now 1 active connections and 1 inactive connections <no request,no span>