@Test public void deferredResultSubscriberWithMultipleValues() throws Exception { // JSON must be preferred for Flux<String> -> List<String> or else we stream this.servletRequest.addHeader("Accept", "application/json"); Bar bar1 = new Bar("foo"); Bar bar2 = new Bar("bar"); EmitterProcessor<Bar> emitter = EmitterProcessor.create(); testDeferredResultSubscriber(emitter, Flux.class, forClass(Bar.class), () -> { emitter.onNext(bar1); emitter.onNext(bar2); emitter.onComplete(); }, Arrays.asList(bar1, bar2)); }
@Test public void failCompleteThenError() { EmitterProcessor<Integer> ep = EmitterProcessor.create(); StepVerifier.create(ep) .then(() -> { ep.onComplete(); ep.onComplete();//noop ep.onError(new Exception("test")); }) .expectComplete() .verifyThenAssertThat() .hasDroppedErrorWithMessage("test"); }
@Test public void simpleReactiveSubscriber() throws InterruptedException { EmitterProcessor<String> str = EmitterProcessor.create(); str.publishOn(asyncGroup) .subscribe(new FooSubscriber()); str.onNext("Goodbye World!"); str.onNext("Goodbye World!"); str.onComplete(); Thread.sleep(500); }
@Test public void streamCanBeCounted() { // "Stream can be counted" // given: "source composables to count and tap" EmitterProcessor<Integer> source = EmitterProcessor.create(); MonoProcessor<Long> tap = source.count() .subscribeWith(MonoProcessor.create()); // when: "the sources accept a value" source.onNext(1); source.onNext(2); source.onNext(3); source.onComplete(); // then: "the count value matches the number of accept" assertThat(tap.peek()).isEqualTo(3); }
@Test public void whenReducingKnownNumberOfValuesOnlyFinalValueIsPassedToConsumers() { // "When reducing a known number of values, only the final value is passed to consumers" // given: "a composable with a known number of values and a reduce function" EmitterProcessor<Integer> source = EmitterProcessor.create(); Mono<Integer> reduced = source.reduce(new Reduction()); List<Integer> values = new ArrayList<>(); reduced.doOnSuccess(values::add).subscribe(); // when: "the expected number of values is accepted" source.onNext(1); source.onNext(2); source.onNext(3); source.onNext(4); source.onNext(5); source.onComplete(); // then: "the consumer only receives the final value" assertThat(values).containsExactly(120); }
@Test public void whenKnownNumberOfValuesIsReducedOnlyFinalValueMadeAvailable() { // "When a known number of values is being reduced, only the final value is made available" // given: "a composable that will accept 2 values and a reduce function" EmitterProcessor<Integer> source = EmitterProcessor.create(); MonoProcessor<Integer> value = source.reduce(new Reduction()) .subscribeWith(MonoProcessor.create()); // when: "the first value is accepted" source.onNext(1); // then: "the reduced value is unknown" assertThat(value.peek()).isNull(); // when: "the second value is accepted" source.onNext(2); source.onComplete(); // then: "the reduced value is known" assertThat(value.peek()).isEqualTo(2); }
@Test public void responseEntityFlux() throws Exception { EmitterProcessor<String> processor = EmitterProcessor.create(); ResponseEntity<Flux<String>> entity = ResponseEntity.ok().body(processor); ResolvableType bodyType = forClassWithGenerics(Flux.class, String.class); MethodParameter type = on(TestController.class).resolveReturnType(ResponseEntity.class, bodyType); this.handler.handleReturnValue(entity, type, this.mavContainer, this.webRequest); assertTrue(this.request.isAsyncStarted()); assertEquals(200, this.response.getStatus()); assertEquals("text/plain", this.response.getContentType()); processor.onNext("foo"); processor.onNext("bar"); processor.onNext("baz"); processor.onComplete(); assertEquals("foobarbaz", this.response.getContentAsString()); }
@Test public void writeText() throws Exception { EmitterProcessor<String> processor = EmitterProcessor.create(); ResponseBodyEmitter emitter = handleValue(processor, Flux.class, forClass(String.class)); EmitterHandler emitterHandler = new EmitterHandler(); emitter.initialize(emitterHandler); processor.onNext("The quick"); processor.onNext(" brown fox jumps over "); processor.onNext("the lazy dog"); processor.onComplete(); assertEquals("The quick brown fox jumps over the lazy dog", emitterHandler.getValuesAsText()); }
@Test public void writeServerSentEvents() throws Exception { this.servletRequest.addHeader("Accept", "text/event-stream"); EmitterProcessor<String> processor = EmitterProcessor.create(); SseEmitter sseEmitter = (SseEmitter) handleValue(processor, Flux.class, forClass(String.class)); EmitterHandler emitterHandler = new EmitterHandler(); sseEmitter.initialize(emitterHandler); processor.onNext("foo"); processor.onNext("bar"); processor.onNext("baz"); processor.onComplete(); assertEquals("data:foo\n\ndata:bar\n\ndata:baz\n\n", emitterHandler.getValuesAsText()); }
@Test public void bufferWillAccumulateMultipleListsOfValues() { //given: "a source and a collected flux" EmitterProcessor<Integer> numbers = EmitterProcessor.create(); //non overlapping buffers EmitterProcessor<Integer> boundaryFlux = EmitterProcessor.create(); MonoProcessor<List<List<Integer>>> res = numbers.buffer(boundaryFlux) .buffer() .publishNext() .toProcessor(); res.subscribe(); numbers.onNext(1); numbers.onNext(2); numbers.onNext(3); boundaryFlux.onNext(1); numbers.onNext(5); numbers.onNext(6); numbers.onComplete(); //"the collected lists are available" assertThat(res.block()).containsExactly(Arrays.asList(1, 2, 3), Arrays.asList(5, 6)); }
@Test public void knownNumberOfValuesCanBeReduced() { // "A known number of values can be reduced" // given: "a composable that will accept 5 values and a reduce function" EmitterProcessor<Integer> source = EmitterProcessor.create(); Mono<Integer> reduced = source.reduce(new Reduction()); MonoProcessor<Integer> value = reduced.subscribeWith(MonoProcessor.create()); // when: "the expected number of values is accepted" source.onNext(1); source.onNext(2); source.onNext(3); source.onNext(4); source.onNext(5); source.onComplete(); // then: "the reduced composable holds the reduced value" assertThat(value.peek()).isEqualTo(120); }
@Test public void windowWillAccumulateMultipleListsOfValues() { //given: "a source and a collected flux" EmitterProcessor<Integer> numbers = EmitterProcessor.create(); //non overlapping buffers EmitterProcessor<Integer> boundaryFlux = EmitterProcessor.create(); MonoProcessor<List<List<Integer>>> res = numbers.window(boundaryFlux) .concatMap(Flux::buffer) .buffer() .publishNext() .toProcessor(); res.subscribe(); numbers.onNext(1); numbers.onNext(2); numbers.onNext(3); boundaryFlux.onNext(1); numbers.onNext(5); numbers.onNext(6); numbers.onComplete(); //"the collected lists are available" assertThat(res.block()).containsExactly(Arrays.asList(1, 2, 3), Arrays.asList(5, 6)); }
@Test public void flatMapUnbounded() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); AtomicInteger emission = new AtomicInteger(); Flux<Integer> source = Flux.range(1, 1000).doOnNext(v -> emission.getAndIncrement()); EmitterProcessor<Integer> source1 = EmitterProcessor.create(); EmitterProcessor<Integer> source2 = EmitterProcessor.create(); source.flatMap(v -> v == 1 ? source1 : source2, Integer.MAX_VALUE, 32).subscribe(ts); Assert.assertEquals(1000, emission.get()); ts.assertNoValues() .assertNoError() .assertNotComplete(); Assert.assertTrue("source1 no subscribers?", source1.downstreamCount() != 0); Assert.assertTrue("source2 no subscribers?", source2.downstreamCount() != 0); source1.onNext(1); source1.onComplete(); source2.onNext(2); source2.onComplete(); ts.assertValueCount(1000) .assertNoError() .assertComplete(); }
@Test public void writeStreamJson() throws Exception { this.servletRequest.addHeader("Accept", "application/stream+json"); EmitterProcessor<Bar> processor = EmitterProcessor.create(); ResponseBodyEmitter emitter = handleValue(processor, Flux.class, forClass(Bar.class)); EmitterHandler emitterHandler = new EmitterHandler(); emitter.initialize(emitterHandler); ServletServerHttpResponse message = new ServletServerHttpResponse(this.servletResponse); emitter.extendResponse(message); Bar bar1 = new Bar("foo"); Bar bar2 = new Bar("bar"); processor.onNext(bar1); processor.onNext(bar2); processor.onComplete(); assertEquals("application/stream+json", message.getHeaders().getContentType().toString()); assertEquals(Arrays.asList(bar1, "\n", bar2, "\n"), emitterHandler.getValues()); }
@Test public void singleSubscriberOnly() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); AtomicInteger emission = new AtomicInteger(); Flux<Integer> source = Flux.range(1, 2).doOnNext(v -> emission.getAndIncrement()); EmitterProcessor<Integer> source1 = EmitterProcessor.create(); EmitterProcessor<Integer> source2 = EmitterProcessor.create(); source.flatMap(v -> v == 1 ? source1 : source2, 1, 32).subscribe(ts); Assert.assertEquals(1, emission.get()); ts.assertNoValues() .assertNoError() .assertNotComplete(); Assert.assertTrue("source1 no subscribers?", source1.downstreamCount() != 0); Assert.assertFalse("source2 has subscribers?", source2.downstreamCount() != 0); source1.onNext(1); source2.onNext(10); source1.onComplete(); source2.onNext(2); source2.onComplete(); ts.assertValues(1, 10, 2) .assertNoError() .assertComplete(); }
@Test public void responseBodyFlux() throws Exception { this.request.addHeader("Accept", "text/event-stream"); MethodParameter type = on(TestController.class).resolveReturnType(Flux.class, String.class); EmitterProcessor<String> processor = EmitterProcessor.create(); this.handler.handleReturnValue(processor, type, this.mavContainer, this.webRequest); assertTrue(this.request.isAsyncStarted()); assertEquals(200, this.response.getStatus()); assertEquals("text/event-stream;charset=UTF-8", this.response.getContentType()); processor.onNext("foo"); processor.onNext("bar"); processor.onNext("baz"); processor.onComplete(); assertEquals("data:foo\n\ndata:bar\n\ndata:baz\n\n", this.response.getContentAsString()); }
@Test public void writeServerSentEventsWithBuilder() throws Exception { ResolvableType type = ResolvableType.forClassWithGenerics(ServerSentEvent.class, String.class); EmitterProcessor<ServerSentEvent<?>> processor = EmitterProcessor.create(); SseEmitter sseEmitter = (SseEmitter) handleValue(processor, Flux.class, type); EmitterHandler emitterHandler = new EmitterHandler(); sseEmitter.initialize(emitterHandler); processor.onNext(ServerSentEvent.builder("foo").id("1").build()); processor.onNext(ServerSentEvent.builder("bar").id("2").build()); processor.onNext(ServerSentEvent.builder("baz").id("3").build()); processor.onComplete(); assertEquals("id:1\ndata:foo\n\nid:2\ndata:bar\n\nid:3\ndata:baz\n\n", emitterHandler.getValuesAsText()); }
@Test public void normalBackpressured() { EmitterProcessor<Integer> tp = EmitterProcessor.create(); StepVerifier.create(tp, 0L) .then(() -> { Assert.assertTrue("No subscribers?", tp.hasDownstreams()); Assert.assertFalse("Completed?", tp.isTerminated()); Assert.assertNull("Has error?", tp.getError()); }) .then(() -> { tp.onNext(1); tp.onNext(2); tp.onComplete(); }) .thenRequest(10L) .expectNext(1, 2) .expectComplete() .verify(); Assert.assertFalse("Subscribers present?", tp.hasDownstreams()); Assert.assertTrue("Not completed?", tp.isTerminated()); Assert.assertNull("Has error?", tp.getError()); }
@Test public void normalAtomicRingBufferBackpressured() { EmitterProcessor<Integer> tp = EmitterProcessor.create(100); StepVerifier.create(tp, 0L) .then(() -> { Assert.assertTrue("No subscribers?", tp.hasDownstreams()); Assert.assertFalse("Completed?", tp.isTerminated()); Assert.assertNull("Has error?", tp.getError()); }) .then(() -> { tp.onNext(1); tp.onNext(2); tp.onComplete(); }) .thenRequest(10L) .expectNext(1, 2) .expectComplete() .verify(); Assert.assertFalse("Subscribers present?", tp.hasDownstreams()); Assert.assertTrue("Not completed?", tp.isTerminated()); Assert.assertNull("Has error?", tp.getError()); }
@Test public void normal() { EmitterProcessor<Integer> tp = EmitterProcessor.create(); StepVerifier.create(tp) .then(() -> { Assert.assertTrue("No subscribers?", tp.hasDownstreams()); Assert.assertFalse("Completed?", tp.isTerminated()); Assert.assertNull("Has error?", tp.getError()); }) .then(() -> { tp.onNext(1); tp.onNext(2); }) .expectNext(1, 2) .then(() -> { tp.onNext(3); tp.onComplete(); }) .expectNext(3) .expectComplete() .verify(); Assert.assertFalse("Subscribers present?", tp.hasDownstreams()); Assert.assertTrue("Not completed?", tp.isTerminated()); Assert.assertNull("Has error?", tp.getError()); }