I am experiencing a problem doing an integration test on a responsive endpoint using spring and Flux. Specifically, it is a simple chat mechanism. I have one endpoint that exposes a Flux object of Messages and a second endpoint that, via post, publishes the message on the flux object. Below are the service class and controller
package com.giane.reactiveangularchat.core.usecases;
import com.giane.reactiveangularchat.core.entities.Message;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Sinks;
@Service
public class MessageService {
private final Sinks.Many<Message> sink;
public MessageService() {
sink = Sinks.many().multicast().directAllOrNothing();
}
public void save(Message message) {
sink.emitNext(message, Sinks.EmitFailureHandler.FAIL_FAST);
sink.tryEmitNext(message);
}
/**
* This method return a Flux of ServerSentEvent. The ServerSentEvent is a reactive stream filtered by user.
* @param user the user to filter the stream
* @return a Flux of ServerSentEvent
*/
public Flux<ServerSentEvent<Message>> getMessage(String project) {
return sink.asFlux()
.filter(message -> message.getTo().equals(project))
.map(message -> ServerSentEvent.builder(message).build());
}
}
package com.giane.reactiveangularchat.entrypoints.rest;
import com.giane.reactiveangularchat.core.entities.Message;
import com.giane.reactiveangularchat.core.usecases.MessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.net.URI;
@RestController
@RequestMapping("/api/messages")
@CrossOrigin(originPatterns = "*", allowedHeaders = "*")
@Slf4j
public class MessageController {
private final MessageService messageService;
public MessageController(MessageService messageService) {
this.messageService = messageService;
}
@PostMapping
public ResponseEntity<Message> postMessage(@RequestBody Message message) {
log.info("Received message {}", message);
messageService.save(message);
return ResponseEntity.created(URI.create("")).body(message);
}
@GetMapping(path = "/stream/{user}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<Message>> getMessages(@PathVariable String user) {
log.info("Received request for user {}", user);
return messageService.getMessage(user);
}
}
My test should involve the following flow.
- Connection to the flux endpoint.
- Sending the message
- Checking the received message on the stream
The following is the test method.
@Test
void contextLoads3() throws InterruptedException {
FluxExchangeResult<Message> fluxFluxExchangeResult = webTestClient.get()
.uri("/api/messages/stream/user1").exchange().expectStatus().is2xxSuccessful().returnResult(Message.class);
Flux<Message> messages = fluxFluxExchangeResult.getResponseBody();
webTestClient.post().uri("/api/messages").bodyValue(new Message("message", "user1")).exchange().expectStatus().is2xxSuccessful();
StepVerifier
.create(messages)
.expectSubscription()
.assertNext(it->{
assertThat(it).isNotNull();
assertThat(it.getMessageContent()).isEqualTo("message");
assertThat(it.getTo()).isEqualTo("user1");
})
.thenCancel()
.verify(Duration.ofSeconds(10));
}
My problem now is that webtestclient's exchange method is blocked until the first event is received. But I cannot send the first event until the exchange method unlocks.
Can someone explain how I can test this use case?