-
Notifications
You must be signed in to change notification settings - Fork 38.4k
WebTestClient gets stuck on exchange when asserting infinite flux #30516
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
The problem apparently only occurs when org.springframework.boot:spring-boot-starter-web is loaded as well - only org.springframework.boot:spring-boot-starter-webflux works fine. |
I also noticed that |
Can you provide a sample please? |
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed. |
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue. |
Hi @mvol have you managed to fix this issue? |
I see the same issue here, when testing an mvc controller with the WebTestClient. @GetMapping(path = "/updates", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter createEmitter() {
SseEmitter emitter = new SseEmitter(-1L);
this.emitters.add(emitter);
emitter.onCompletion(() -> this.emitters.remove(emitter));
emitter.onTimeout(() -> {
emitter.complete();
this.emitters.remove(emitter);
});
return emitter;
} Setup is SpringBootTest with WebEnvironment @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
public class MessageControllerIntegrationTest {
@Autowired
WebTestClient client; Test gets stuck with timeout in exchange: var updateStream = client
.get()
.uri("/api/messages/updates")
.headers(httpHeaders -> httpHeaders.setBearerAuth(jwtAuth))
.accept(MediaType.TEXT_EVENT_STREAM)
.exchange()
.expectStatus().isOk()
.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM)
.returnResult(ServerSentEvent.class)
.getResponseBody(); Let me know if I can provide further help. I hope to see this isse re-opened. |
I just created a MRE at https://github.com/eiswind/webtestclient-sse |
@rstoyanchev could you consider re-opening this? |
Facing the exact same problem. Did anybody find a suitable solution? |
I think this is quite un-fixable as WebTestClient calls block() internally while there is no event yet. I created myself a workaround using the standard WebFlux WebClient, works like a charm. |
@eiswind Can you please share the workaround? I am also facing the same issue after springboot uprade. |
There is a limitation in using
For infinite streams with Spring MVC, you'll need a running server, i.e. |
The issue at my side was that my SSE Endpoint does send the first event only after a certain trigger happens. When I use WebTestClient, it blocks internally waiting for a response, at least as far as I understand it. This prevents triggering the SSE Event before the WebTestClient goes into a timeout. I solved it using the regular WebClient for the SSE Endpoint. ParameterizedTypeReference<ServerSentEvent<MessagePushNotification>> type = new ParameterizedTypeReference<>() {
};
var subscription = webClientBuilder.baseUrl("http://localhost:" + port).build()
.get()
.uri("/api/messages/updates")
.headers(httpHeaders -> httpHeaders.setBearerAuth(jwtAuth))
.retrieve().bodyToFlux(type).subscribe(event -> {
if (event.data().type() == MessagePushNotificationType.ADD) {
eventCount.addAndGet(1);
}
}); |
That's essentially the same as using |
I just tried once again like: ParameterizedTypeReference<ServerSentEvent<MessagePushNotification>> type = new ParameterizedTypeReference<>() {
};
var result = webTestClient
.get()
.uri("/api/messages/updates")
.headers(httpHeaders -> httpHeaders.setBearerAuth(jwtAuth))
.exchange().returnResult(type);
var subscription = result.getResponseBody().subscribe(event -> {
if (event.data().type() == MessagePushNotificationType.ADD) {
eventCount.addAndGet(1);
}
}); That still gives me a timeout on the exchange. Maybe I don't get what is meant by a live-server? My test goes like @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
public class MessageControllerIntegrationTest {
@Autowired
WebTestClient client; |
Yes that should be using a live server. I would use |
The SSE endpoint in the example does NOT sent an event until we make another (different endpoint) request like: @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Message> saveMessage(
@RequestBody @Valid CreateOrEditMessage coe
) {
var clonedMessage = new Message(coe.likes(), coe.text(), LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS), null);
var savedMessage = messageRepository.save(clonedMessage);
sendUpdateNotification(new MessagePushNotification(MessagePushNotificationType.ADD, savedMessage));
var uri = URI.create("/api/messages/" + savedMessage.id());
return ResponseEntity.created(uri).body(savedMessage);
} I updated the reproducer from above. Here you can see that the WebTestClient blocks into a timeout. |
There are two things. In the referenced example, you don't actually emit any events at all, but generally for an infinite stream, the server does not complete the response on its own. You need to do so from the client side after consuming events that you are interested in with For the specific scenario you mention where you need to start the stream, then make a separate HTTP post, @Test
void entityStream() {
Flux<Person> personFlux = Flux.defer(() ->
this.client.get()
.accept(TEXT_EVENT_STREAM)
.exchange()
.expectStatus().isOk()
.expectHeader().contentTypeCompatibleWith(TEXT_EVENT_STREAM)
.returnResult(Person.class)
.getResponseBody()
)
.subscribeOn(Schedulers.boundedElastic());
StepVerifier.create(personFlux)
.then(() -> {
// Make HTTP Post
})
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
.expectNextCount(4)
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
.thenCancel()
.verify();
} That said, as a scenario with multiple HTTP calls, perhaps |
@rstoyanchev Thanks for taking your time and clearing things up a little bit. The unexcpected thing here to me was that it's synchronous and blocking even if I return a Flux. Using the WebClient allows me to do what I need and this ticket should give enough hints to others how might follow up. |
Fair point, and you generally do get the |
Affects: 6.0.8
Problem:
When trying to assert a controller-method returning an infinite flux of server-sent-events, WebTestClient gets stuck on
exchange
.Expected behaviour: StepVerifier asserts first three items, then cancels the subscription, test finishes successfully.
Actual behaviour: WebTestClient gets stuck on
exchange
-expectStatus
is never executed, test never finishes.Example:
The text was updated successfully, but these errors were encountered: