@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 acceptedValuesArePassedToRegisteredConsumer() { // "Accepted values are passed to a registered Consumer" // given: "a composable with a registered consumer" EmitterProcessor<Integer> composable = EmitterProcessor.create(); AtomicReference<Integer> value = new AtomicReference<>(); composable.subscribe(value::set); // when: "a value is accepted" composable.onNext(1); // then: "it is passed to the consumer" assertThat(value.get()).isEqualTo(1); // when: "another value is accepted" composable.onNext(2); // then: "it too is passed to the consumer" assertThat(value.get()).isEqualTo(2); }
@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 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 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 whenAcceptedEventIsIterableSplitCanIterateOverValues() { // "When the accepted event is Iterable, split can iterate over values" // given: "a composable with a known number of values" EmitterProcessor<Iterable<String>> d = EmitterProcessor.create(); Flux<String> composable = d.flatMap(Flux::fromIterable); // when: "accept list of Strings" AtomicReference<String> tap = new AtomicReference<>(); composable.subscribe(tap::set); d.onNext(Arrays.asList("a", "b", "c")); // then: "its value is the last of the initial values" assertThat(tap.get()).isEqualTo("c"); }
@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 fluxValuesCanBeMapped() { // "A Flux"s values can be mapped" // given: "a source composable with a mapping function" EmitterProcessor<Integer> source = EmitterProcessor.create(); Flux<Integer> mapped = source.map(it -> it * 2); // when: "the source accepts a value" AtomicReference<Integer> value = new AtomicReference<>(); mapped.subscribe(value::set); source.onNext(1); // then: "the value is mapped" assertThat(value.get()).isEqualTo(2); }
@Test public void someFirst() { EmitterProcessor<Integer> tp = EmitterProcessor.create(); AssertSubscriber<Integer> ts = AssertSubscriber.create(); tp.onErrorResume(v -> Flux.range(11, 10)) .subscribe(ts); tp.onNext(1); tp.onNext(2); tp.onNext(3); tp.onNext(4); tp.onNext(5); tp.onError(new RuntimeException("forced failure")); ts.assertValues(1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) .assertNoError() .assertComplete(); }
@Test(expected = NullPointerException.class) public void onNextNull() { EmitterProcessor.create().onNext(null); }
@Test(expected = NullPointerException.class) public void failNullNext() { EmitterProcessor.create().onNext(null); }
@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 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 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 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 error() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); EmitterProcessor<Integer> e = EmitterProcessor.create(); ConnectableFlux<Integer> p = e.publish(); p.subscribe(ts); p.connect(); e.onNext(1); e.onNext(2); e.onError(new RuntimeException("forced failure")); ts.assertValues(1, 2) .assertError(RuntimeException.class) .assertErrorWith( x -> Assert.assertTrue(x.getMessage().contains("forced failure"))) .assertNotComplete(); }
@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 disconnect() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); EmitterProcessor<Integer> e = EmitterProcessor.create(); ConnectableFlux<Integer> p = e.publish(); p.subscribe(ts); Disposable r = p.connect(); e.onNext(1); e.onNext(2); r.dispose(); ts.assertValues(1, 2) .assertError(CancellationException.class) .assertNotComplete(); Assert.assertFalse("sp has subscribers?", e.downstreamCount() != 0); }