I am working on a project that uses the Spring WebFlux stack. We have a Controller where you can subscribe for updates on a specific object. This Controller returns an EmitterProcessor
where clients can subscribe to. When something is published on the EmitterProcessor
, subscribed clients get notified.
This works well in practice, but my unit test is failing. The unit test uses the WebTestClient, which blocks when an exchange()
operation returns a EmitterProcessor
(also tried with other FluxProcessor
implementations such as UnicastProcessor
). The error I get is the following:
java.lang.IllegalStateException: Timeout on blocking read for 5000 MILLISECONDS
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:117)
at reactor.core.publisher.Mono.block(Mono.java:1524)
at org.springframework.test.web.reactive.server.DefaultWebTestClient$DefaultRequestBodyUriSpec.exchange(DefaultWebTestClient.java:283)
I found this thread, which also reports that the exchange()
method on the WebTestClient
blocks, but as explained there it blocks only for retrieving the status and headers, so this should not be the problem. Also, in the case that a Flux
is returned, this works fine, as demonstrated by the referenced testcase.
Test case
I created a simple test case derived from the referenced test case and adapted it to return a Flux
in one case and an EmitterProcessor
in another case (which fails). As you might notice, the assertions for the EmitterProcessor
should fail it, but it never gets there because of the blocking exchange()
call.
Also notice that when I uncomment the line processor.onNext("hello");
, this content is returned and the testcase succeeds.
package test;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE;
import org.junit.Test;
import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.EmitterProcessor;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
public class ProcessorTest {
private final WebTestClient client = WebTestClient.bindToController(new StringController()).configureClient().build();
@Test
public void entityStream() {
FluxExchangeResult<String> result = client //
.get().uri("/flux") //
.accept(TEXT_EVENT_STREAM) //
.exchange() //
.expectStatus().isOk() //
.expectHeader().contentTypeCompatibleWith(TEXT_EVENT_STREAM) //
.returnResult(String.class);
StepVerifier.create(result.getResponseBody()) //
.expectNext("hello") //
.thenCancel() //
.verify();
}
@Test
public void entityStreamProcessor() {
FluxExchangeResult<String> result = client //
.get().uri("/processor") //
.accept(TEXT_EVENT_STREAM) //
.exchange() //
.expectStatus().isOk() //
.expectHeader().contentTypeCompatibleWith(TEXT_EVENT_STREAM) //
.returnResult(String.class);
StepVerifier.create(result.getResponseBody()) //
.expectNext("hello") //
.thenCancel() //
.verify();
}
@RestController
static class StringController {
@GetMapping(value = "/flux", produces = TEXT_EVENT_STREAM_VALUE)
Flux<String> getFlux() {
return Flux.just("hello");
}
@GetMapping(value = "/processor", produces = TEXT_EVENT_STREAM_VALUE)
Flux<String> getProcessor() {
EmitterProcessor<String> processor = EmitterProcessor.create();
// processor.onNext("hello");
return processor;
}
}
}