diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpExchangeAdapterServiceMethodTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpExchangeAdapterServiceMethodTests.java deleted file mode 100644 index 053933e731..0000000000 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpExchangeAdapterServiceMethodTests.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.service.invoker; - -import org.junit.jupiter.api.BeforeEach; - -/** - * Tests for {@link HttpServiceMethod} with a blocking test {@link TestExchangeAdapter} that - * stubs the client invocations. - *

- * The tests do not create or invoke {@code HttpServiceMethod} directly but rather use - * {@link HttpServiceProxyFactory} to create a service proxy in order to use a strongly - * typed interface without the need for class casts. - * - * @author Olga Maciaszek-Sharma - */ -class HttpExchangeAdapterServiceMethodTests extends HttpServiceMethodTests { - - @BeforeEach - void setUp() { - this.client = new TestExchangeAdapter(); - this.proxyFactory = HttpServiceProxyFactory.builderFor(this.client).build(); - } - -} diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java index db3525d98b..eab3a1c425 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java @@ -19,9 +19,16 @@ package org.springframework.web.service.invoker; import java.util.List; import java.util.Optional; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.core.Single; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -36,29 +43,35 @@ import static org.springframework.http.MediaType.APPLICATION_CBOR_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; /** - * Base class for testing {@link HttpServiceMethod} with a test {@link TestExchangeAdapter} - * and a test {@link TestExchangeAdapter} that stub the client invocations. + * Tests for {@link HttpServiceMethod} with + * {@link TestExchangeAdapter} and {@link TestReactorExchangeAdapter}. * - *

- * The tests do not create or invoke {@code HttpServiceMethod} directly but rather use - * {@link HttpServiceProxyFactory} to create a service proxy in order to use a strongly - * typed interface without the need for class casts. + *

The tests do not create or invoke {@code HttpServiceMethod} directly but + * rather use {@link HttpServiceProxyFactory} to create a service proxy in order to + * use a strongly typed interface without the need for class casts. * * @author Rossen Stoyanchev * @author Olga Maciaszek-Sharma */ -abstract class HttpServiceMethodTests { +class HttpServiceMethodTests { - protected static final ParameterizedTypeReference BODY_TYPE = new ParameterizedTypeReference<>() { - }; + private static final ParameterizedTypeReference BODY_TYPE = new ParameterizedTypeReference<>() {}; - protected TestExchangeAdapter client; - protected HttpServiceProxyFactory proxyFactory; + private final TestExchangeAdapter client = new TestExchangeAdapter(); + + private final TestReactorExchangeAdapter reactorClient = new TestReactorExchangeAdapter(); + + private final HttpServiceProxyFactory proxyFactory = + HttpServiceProxyFactory.builder().exchangeAdapter(this.client).build(); + + private final HttpServiceProxyFactory reactorProxyFactory = + HttpServiceProxyFactory.builder().exchangeAdapter(this.reactorClient).build(); + @Test - void blockingService() { - BlockingService service = this.proxyFactory.createClient(BlockingService.class); + void service() { + Service service = this.proxyFactory.createClient(Service.class); service.execute(); @@ -66,19 +79,82 @@ abstract class HttpServiceMethodTests { assertThat(headers).isNotNull(); String body = service.getBody(); - assertThat(body).isEqualTo(client.getInvokedMethodName()); + assertThat(body).isEqualTo(this.client.getInvokedMethodName()); Optional optional = service.getBodyOptional(); - assertThat(optional.get()).startsWith("exchangeForBody"); + assertThat(optional.get()).isEqualTo("exchangeForBody"); ResponseEntity entity = service.getEntity(); - assertThat(entity.getBody()).startsWith("exchangeForEntity"); + assertThat(entity.getBody()).isEqualTo("exchangeForEntity"); ResponseEntity voidEntity = service.getVoidEntity(); assertThat(voidEntity.getBody()).isNull(); List list = service.getList(); - assertThat(list.get(0)).startsWith("exchangeForBody"); + assertThat(list.get(0)).isEqualTo("exchangeForBody"); + } + + @Test + void reactorService() { + ReactorService service = this.reactorProxyFactory.createClient(ReactorService.class); + + Mono voidMono = service.execute(); + StepVerifier.create(voidMono).verifyComplete(); + verifyReactorClientInvocation("exchangeForMono", null); + + Mono headersMono = service.getHeaders(); + StepVerifier.create(headersMono).expectNextCount(1).verifyComplete(); + verifyReactorClientInvocation("exchangeForHeadersMono", null); + + Mono body = service.getBody(); + StepVerifier.create(body).expectNext("exchangeForBodyMono").verifyComplete(); + verifyReactorClientInvocation("exchangeForBodyMono", BODY_TYPE); + + Flux fluxBody = service.getFluxBody(); + StepVerifier.create(fluxBody).expectNext("exchange", "For", "Body", "Flux").verifyComplete(); + verifyReactorClientInvocation("exchangeForBodyFlux", BODY_TYPE); + + Mono> voidEntity = service.getVoidEntity(); + StepVerifier.create(voidEntity).expectNext(ResponseEntity.ok().build()).verifyComplete(); + verifyReactorClientInvocation("exchangeForBodilessEntityMono", null); + + Mono> entity = service.getEntity(); + StepVerifier.create(entity).expectNext(ResponseEntity.ok("exchangeForEntityMono")); + verifyReactorClientInvocation("exchangeForEntityMono", BODY_TYPE); + + Mono>> fluxEntity = service.getFluxEntity(); + StepVerifier.create(fluxEntity.flatMapMany(HttpEntity::getBody)) + .expectNext("exchange", "For", "Entity", "Flux") + .verifyComplete(); + verifyReactorClientInvocation("exchangeForEntityFlux", BODY_TYPE); + + assertThat(service.getDefaultMethodValue()).isEqualTo("default value"); + } + + @Test + void rxJavaService() { + RxJavaService service = this.reactorProxyFactory.createClient(RxJavaService.class); + Completable completable = service.execute(); + assertThat(completable).isNotNull(); + + Single headersSingle = service.getHeaders(); + assertThat(headersSingle.blockingGet()).isNotNull(); + + Single bodySingle = service.getBody(); + assertThat(bodySingle.blockingGet()).isEqualTo("exchangeForBodyMono"); + + Flowable bodyFlow = service.getFlowableBody(); + assertThat(bodyFlow.toList().blockingGet()).asList().containsExactly("exchange", "For", "Body", "Flux"); + + Single> voidEntity = service.getVoidEntity(); + assertThat(voidEntity.blockingGet().getBody()).isNull(); + + Single> entitySingle = service.getEntity(); + assertThat(entitySingle.blockingGet().getBody()).isEqualTo("exchangeForEntityMono"); + + Single>> entityFlow = service.getFlowableEntity(); + Flowable body = (entityFlow.blockingGet()).getBody(); + assertThat(body.toList().blockingGet()).containsExactly("exchange", "For", "Entity", "Flux"); } @Test @@ -128,13 +204,14 @@ abstract class HttpServiceMethodTests { assertThat(requestValues.getHeaders().getAccept()).containsExactly(MediaType.APPLICATION_JSON); } - protected void verifyClientInvocation(String methodName, @Nullable ParameterizedTypeReference expectedBodyType) { - assertThat(this.client.getInvokedMethodName()).isEqualTo(methodName); - assertThat(this.client.getBodyType()).isEqualTo(expectedBodyType); + protected void verifyReactorClientInvocation(String methodName, @Nullable ParameterizedTypeReference expectedBodyType) { + assertThat(this.reactorClient.getInvokedMethodName()).isEqualTo(methodName); + assertThat(this.reactorClient.getBodyType()).isEqualTo(expectedBodyType); } + @SuppressWarnings("unused") - private interface BlockingService { + private interface Service { @GetExchange void execute(); @@ -159,6 +236,64 @@ abstract class HttpServiceMethodTests { } + + private interface ReactorService { + + @GetExchange + Mono execute(); + + @GetExchange + Mono getHeaders(); + + @GetExchange + Mono getBody(); + + @GetExchange + Flux getFluxBody(); + + @GetExchange + Mono> getVoidEntity(); + + @GetExchange + Mono> getEntity(); + + @GetExchange + Mono>> getFluxEntity(); + + default String getDefaultMethodValue() { + return "default value"; + } + + } + + + @SuppressWarnings("unused") + private interface RxJavaService { + + @GetExchange + Completable execute(); + + @GetExchange + Single getHeaders(); + + @GetExchange + Single getBody(); + + @GetExchange + Flowable getFlowableBody(); + + @GetExchange + Single> getVoidEntity(); + + @GetExchange + Single> getEntity(); + + @GetExchange + Single>> getFlowableEntity(); + + } + + @SuppressWarnings("unused") private interface MethodLevelAnnotatedService { @@ -173,7 +308,6 @@ abstract class HttpServiceMethodTests { @SuppressWarnings("unused") @HttpExchange(url = "${baseUrl}", contentType = APPLICATION_CBOR_VALUE, accept = APPLICATION_CBOR_VALUE) private interface TypeAndMethodLevelAnnotatedService extends MethodLevelAnnotatedService { - } } diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/ReactiveHttpServiceMethodTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/ReactiveHttpServiceMethodTests.java deleted file mode 100644 index 83cb110c29..0000000000 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/ReactiveHttpServiceMethodTests.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.service.invoker; - -import io.reactivex.rxjava3.core.Completable; -import io.reactivex.rxjava3.core.Flowable; -import io.reactivex.rxjava3.core.Single; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.web.service.annotation.GetExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Base class for testing reactive scenarios in {@link HttpServiceMethod} with - * a test {@link TestReactorExchangeAdapter} that stub the client invocations. - * - *

- * The tests do not create or invoke {@code HttpServiceMethod} directly but rather use - * {@link HttpServiceProxyFactory} to create a service proxy in order to use a strongly - * typed interface without the need for class casts. - * - * @author Rossen Stoyanchev - * @author Olga Maciaszek-Sharma - */ -abstract class ReactiveHttpServiceMethodTests extends HttpServiceMethodTests { - - @Test - void reactorService() { - ReactorService service = proxyFactory.createClient(ReactorService.class); - - Mono voidMono = service.execute(); - StepVerifier.create(voidMono).verifyComplete(); - verifyClientInvocation("exchangeForMono", null); - - Mono headersMono = service.getHeaders(); - StepVerifier.create(headersMono).expectNextCount(1).verifyComplete(); - verifyClientInvocation("exchangeForHeadersMono", null); - - Mono body = service.getBody(); - StepVerifier.create(body).expectNext("exchangeForBodyMono").verifyComplete(); - verifyClientInvocation("exchangeForBodyMono", BODY_TYPE); - - Flux fluxBody = service.getFluxBody(); - StepVerifier.create(fluxBody).expectNext("exchange", "For", "Body", "Flux").verifyComplete(); - verifyClientInvocation("exchangeForBodyFlux", BODY_TYPE); - - Mono> voidEntity = service.getVoidEntity(); - StepVerifier.create(voidEntity).expectNext(ResponseEntity.ok().build()).verifyComplete(); - verifyClientInvocation("exchangeForBodilessEntityMono", null); - - Mono> entity = service.getEntity(); - StepVerifier.create(entity).expectNext(ResponseEntity.ok("exchangeForEntityMono")); - verifyClientInvocation("exchangeForEntityMono", BODY_TYPE); - - Mono>> fluxEntity = service.getFluxEntity(); - StepVerifier.create(fluxEntity.flatMapMany(HttpEntity::getBody)) - .expectNext("exchange", "For", "Entity", "Flux") - .verifyComplete(); - verifyClientInvocation("exchangeForEntityFlux", BODY_TYPE); - - assertThat(service.getDefaultMethodValue()).isEqualTo("default value"); - } - - @Test - void rxJavaService() { - RxJavaService service = this.proxyFactory.createClient(RxJavaService.class); - Completable completable = service.execute(); - assertThat(completable).isNotNull(); - - Single headersSingle = service.getHeaders(); - assertThat(headersSingle.blockingGet()).isNotNull(); - - Single bodySingle = service.getBody(); - assertThat(bodySingle.blockingGet()).isEqualTo("exchangeForBodyMono"); - - Flowable bodyFlow = service.getFlowableBody(); - assertThat(bodyFlow.toList().blockingGet()).asList().containsExactly("exchange", "For", "Body", "Flux"); - - Single> voidEntity = service.getVoidEntity(); - assertThat(voidEntity.blockingGet().getBody()).isNull(); - - Single> entitySingle = service.getEntity(); - assertThat(entitySingle.blockingGet().getBody()).isEqualTo("exchangeForEntityMono"); - - Single>> entityFlow = service.getFlowableEntity(); - Flowable body = (entityFlow.blockingGet()).getBody(); - assertThat(body.toList().blockingGet()).containsExactly("exchange", "For", "Entity", "Flux"); - } - - private interface ReactorService { - - @GetExchange - Mono execute(); - - @GetExchange - Mono getHeaders(); - - @GetExchange - Mono getBody(); - - @GetExchange - Flux getFluxBody(); - - @GetExchange - Mono> getVoidEntity(); - - @GetExchange - Mono> getEntity(); - - @GetExchange - Mono>> getFluxEntity(); - - default String getDefaultMethodValue() { - return "default value"; - } - - } - - @SuppressWarnings("unused") - private interface RxJavaService { - - @GetExchange - Completable execute(); - - @GetExchange - Single getHeaders(); - - @GetExchange - Single getBody(); - - @GetExchange - Flowable getFlowableBody(); - - @GetExchange - Single> getVoidEntity(); - - @GetExchange - Single> getEntity(); - - @GetExchange - Single>> getFlowableEntity(); - - } - -} diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/ReactorExchangeAdapterHttpServiceMethodTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/ReactorExchangeAdapterHttpServiceMethodTests.java deleted file mode 100644 index 8a7aecda3e..0000000000 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/ReactorExchangeAdapterHttpServiceMethodTests.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.service.invoker; - -import org.junit.jupiter.api.BeforeEach; - -/** - * Tests for {@link HttpServiceMethod} with an {@link HttpExchangeAdapter} - * build from a test {@link TestReactorExchangeAdapter} that stubs the client invocations. - *

- * The tests do not create or invoke {@code HttpServiceMethod} directly but rather use - * {@link HttpServiceProxyFactory} to create a service proxy in order to use a strongly - * typed interface without the need for class casts. - * - * @author Olga Maciaszek-Sharma - */ -public class ReactorExchangeAdapterHttpServiceMethodTests extends ReactiveHttpServiceMethodTests { - - @BeforeEach - void setUp() { - this.client = new TestReactorExchangeAdapter(); - this.proxyFactory = HttpServiceProxyFactory.builder().exchangeAdapter(this.client).build(); - } - -}