BAEL-3091: The Prototype Pattern in Java (changed code based on valid comments from a reader)

This commit is contained in:
Vivek Balasubramaniam
2019-10-29 22:27:15 +05:30
parent db85c8f275
commit d3d5b060e7
20517 changed files with 1642290 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
# Files #
*.log
+10
View File
@@ -0,0 +1,10 @@
## Spring 5 WebFlux
This module contains articles about Spring 5 WebFlux
## Relevant articles:
- [Spring Boot Reactor Netty Configuration](https://www.baeldung.com/spring-boot-reactor-netty)
- [How to Return 404 with Spring WebFlux](https://www.baeldung.com/spring-webflux-404)
- [Spring WebClient Requests with Parameters](https://www.baeldung.com/webflux-webclient-parameters)
- [RSocket Using Spring Boot](https://www.baeldung.com/spring-boot-rsocket)
+104
View File
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baeldung</groupId>
<artifactId>spring-5-webflux</artifactId>
<version>1.0-SNAPSHOT</version>
<name>spring-5-webflux</name>
<url>http://www.baeldung.com</url>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../parent-boot-2</relativePath>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-rsocket</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
<properties>
<spring-boot.version>2.2.0.M3</spring-boot.version>
</properties>
</project>
@@ -0,0 +1,64 @@
package com.baeldung.spring.responsestatus;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
@RequestMapping("/statuses")
@RestController
public class ResponseStatusController {
@GetMapping(value = "/ok", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Flux<String> ok() {
return Flux.just("ok");
}
@GetMapping(value = "/no-content", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseStatus(HttpStatus.NO_CONTENT)
public Flux<String> noContent() {
return Flux.empty();
}
@GetMapping(value = "/accepted", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Flux<String> accepted(ServerHttpResponse response) {
response.setStatusCode(HttpStatus.ACCEPTED);
return Flux.just("accepted");
}
@GetMapping(value = "/bad-request")
public Mono<String> badRequest() {
return Mono.error(new IllegalArgumentException());
}
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Illegal arguments")
@ExceptionHandler(IllegalArgumentException.class)
public void illegalArgument() {
}
@GetMapping(value = "/unauthorized")
public ResponseEntity<Mono<String>> unathorized() {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.header("X-Reason", "user-invalid")
.body(Mono.just("unauthorized"));
}
@Bean
public RouterFunction<ServerResponse> notFound() {
return RouterFunctions.route(GET("/statuses/not-found"), request -> ServerResponse
.notFound()
.build());
}
}
@@ -0,0 +1,12 @@
package com.baeldung.spring.responsestatus;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringResponseStatusApp {
public static void main(String[] args) {
SpringApplication.run(SpringResponseStatusApp.class, args);
}
}
@@ -0,0 +1,16 @@
package com.baeldung.spring.rsocket.client;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.main(ClientApplication.class)
.sources(ClientApplication.class)
.profiles("client")
.run(args);
}
}
@@ -0,0 +1,30 @@
package com.baeldung.spring.rsocket.client;
import io.rsocket.RSocket;
import io.rsocket.RSocketFactory;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.transport.netty.client.TcpClientTransport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.util.MimeTypeUtils;
@Configuration
public class ClientConfiguration {
@Bean
public RSocket rSocket() {
return RSocketFactory.connect()
.mimeType(MimeTypeUtils.APPLICATION_JSON_VALUE, MimeTypeUtils.APPLICATION_JSON_VALUE)
.frameDecoder(PayloadDecoder.ZERO_COPY)
.transport(TcpClientTransport.create(7000))
.start()
.block();
}
@Bean
RSocketRequester rSocketRequester(RSocketStrategies rSocketStrategies) {
return RSocketRequester.wrap(rSocket(), MimeTypeUtils.APPLICATION_JSON, rSocketStrategies);
}
}
@@ -0,0 +1,47 @@
package com.baeldung.spring.rsocket.client;
import com.baeldung.spring.rsocket.model.MarketData;
import com.baeldung.spring.rsocket.model.MarketDataRequest;
import java.util.Random;
import org.reactivestreams.Publisher;
import org.springframework.http.MediaType;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MarketDataRestController {
private final Random random = new Random();
private final RSocketRequester rSocketRequester;
public MarketDataRestController(RSocketRequester rSocketRequester) {
this.rSocketRequester = rSocketRequester;
}
@GetMapping(value = "/current/{stock}")
public Publisher<MarketData> current(@PathVariable("stock") String stock) {
return rSocketRequester.route("currentMarketData")
.data(new MarketDataRequest(stock))
.retrieveMono(MarketData.class);
}
@GetMapping(value = "/feed/{stock}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Publisher<MarketData> feed(@PathVariable("stock") String stock) {
return rSocketRequester.route("feedMarketData")
.data(new MarketDataRequest(stock))
.retrieveFlux(MarketData.class);
}
@GetMapping(value = "/collect")
public Publisher<Void> collect() {
return rSocketRequester.route("collectMarketData")
.data(getMarketData())
.send();
}
private MarketData getMarketData() {
return new MarketData("X", random.nextInt(10));
}
}
@@ -0,0 +1,20 @@
package com.baeldung.spring.rsocket.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MarketData {
private String stock;
private int currentPrice;
public static MarketData fromException(Exception e) {
MarketData marketData = new MarketData();
marketData.setStock(e.getMessage());
return marketData;
}
}
@@ -0,0 +1,13 @@
package com.baeldung.spring.rsocket.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MarketDataRequest {
private String stock;
}
@@ -0,0 +1,40 @@
package com.baeldung.spring.rsocket.server;
import com.baeldung.spring.rsocket.model.MarketData;
import com.baeldung.spring.rsocket.model.MarketDataRequest;
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Controller
public class MarketDataRSocketController {
private final MarketDataRepository marketDataRepository;
public MarketDataRSocketController(MarketDataRepository marketDataRepository) {
this.marketDataRepository = marketDataRepository;
}
@MessageMapping("currentMarketData")
public Mono<MarketData> currentMarketData(MarketDataRequest marketDataRequest) {
return marketDataRepository.getOne(marketDataRequest.getStock());
}
@MessageMapping("feedMarketData")
public Flux<MarketData> feedMarketData(MarketDataRequest marketDataRequest) {
return marketDataRepository.getAll(marketDataRequest.getStock());
}
@MessageMapping("collectMarketData")
public Mono<Void> collectMarketData(MarketData marketData) {
marketDataRepository.add(marketData);
return Mono.empty();
}
@MessageExceptionHandler
public Mono<MarketData> handleException(Exception e) {
return Mono.just(MarketData.fromException(e));
}
}
@@ -0,0 +1,37 @@
package com.baeldung.spring.rsocket.server;
import com.baeldung.spring.rsocket.model.MarketData;
import java.time.Duration;
import java.util.Random;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class MarketDataRepository {
private static final int BOUND = 100;
private Random random = new Random();
public Flux<MarketData> getAll(String stock) {
return Flux.fromStream(Stream.generate(() -> getMarketDataResponse(stock)))
.log()
.delayElements(Duration.ofSeconds(1));
}
public Mono<MarketData> getOne(String stock) {
return Mono.just(getMarketDataResponse(stock));
}
public void add(MarketData marketData) {
log.info("New market data: {}", marketData);
}
private MarketData getMarketDataResponse(String stock) {
return new MarketData(stock, random.nextInt(BOUND));
}
}
@@ -0,0 +1,16 @@
package com.baeldung.spring.rsocket.server;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication
public class ServerApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.main(ServerApplication.class)
.sources(ServerApplication.class)
.profiles("server")
.run(args);
}
}
@@ -0,0 +1,36 @@
package com.baeldung.spring.serverconfig;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.embedded.netty.NettyServerCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import reactor.netty.http.server.HttpServer;
@Configuration
@Profile("skipAutoConfig")
public class CustomNettyWebServerFactory {
@Bean
public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {
NettyReactiveWebServerFactory webServerFactory = new NettyReactiveWebServerFactory();
webServerFactory.addServerCustomizers(new EventLoopNettyCustomizer());
return webServerFactory;
}
private static class EventLoopNettyCustomizer implements NettyServerCustomizer {
@Override
public HttpServer apply(HttpServer httpServer) {
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
return httpServer
.tcpConfiguration(tcpServer -> tcpServer.bootstrap(
serverBootstrap -> serverBootstrap.group(parentGroup, childGroup).channel(NioServerSocketChannel.class)
));
}
}
}
@@ -0,0 +1,23 @@
package com.baeldung.spring.serverconfig;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/greet")
public class GreetingController {
private final GreetingService greetingService;
public GreetingController(GreetingService greetingService) {
this.greetingService = greetingService;
}
@GetMapping("/{name}")
private Mono<String> greet(@PathVariable String name) {
return greetingService.greet(name);
}
}
@@ -0,0 +1,12 @@
package com.baeldung.spring.serverconfig;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
public class GreetingService {
public Mono<String> greet(String name) {
return Mono.just("Greeting " + name);
}
}
@@ -0,0 +1,30 @@
package com.baeldung.spring.serverconfig;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.embedded.netty.NettyServerCustomizer;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
import reactor.netty.http.server.HttpServer;
@Component
public class NettyWebServerFactoryPortCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
@Override
public void customize(NettyReactiveWebServerFactory serverFactory) {
serverFactory.addServerCustomizers(new PortCustomizer(8443));
}
private static class PortCustomizer implements NettyServerCustomizer {
private final int port;
private PortCustomizer(int port) {
this.port = port;
}
@Override
public HttpServer apply(HttpServer httpServer) {
return httpServer.port(port);
}
}
}
@@ -0,0 +1,26 @@
package com.baeldung.spring.serverconfig;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.embedded.netty.SslServerCustomizer;
import org.springframework.boot.web.server.Http2;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class NettyWebServerFactorySslCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
@Override
public void customize(NettyReactiveWebServerFactory serverFactory) {
Ssl ssl = new Ssl();
ssl.setEnabled(true);
ssl.setKeyStore("classpath:sample.jks");
ssl.setKeyAlias("alias");
ssl.setKeyPassword("password");
ssl.setKeyStorePassword("secret");
Http2 http2 = new Http2();
http2.setEnabled(false);
serverFactory.addServerCustomizers(new SslServerCustomizer(ssl, http2, null));
serverFactory.setPort(8443);
}
}
@@ -0,0 +1,12 @@
package com.baeldung.spring.serverconfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ServerConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ServerConfigApplication.class, args);
}
}
@@ -0,0 +1,12 @@
package com.baeldung.spring.webclientrequests;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringWebClientRequestsApp {
public static void main(String[] args) {
SpringApplication.run(SpringWebClientRequestsApp.class, args);
}
}
@@ -0,0 +1 @@
server.port=8080
@@ -0,0 +1,2 @@
spring.rsocket.server.port=7000
server.port=8081
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="Console"
class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{1.}): %msg%n%throwable
</Pattern>
</layout>
</appender>
<appender name="AccessLog" class="ch.qos.logback.core.FileAppender">
<file>netty-access.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<appender name="Async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="AccessLog"/>
</appender>
<logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
<appender-ref ref="Console"/>
<appender-ref ref="Async"/>
</logger>
<root level="info">
<appender-ref ref="Console"/>
</root>
</configuration>
Binary file not shown.
@@ -0,0 +1,70 @@
package com.baeldung.spring.responsestatus;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ResponseStatusControllerLiveTest {
@Autowired
private WebTestClient testClient;
@Test
public void whenCallRest_thenStatusIsOk() {
testClient.get()
.uri("/statuses/ok")
.exchange()
.expectStatus()
.isOk();
}
@Test
public void whenCallRest_thenStatusIsNoContent() {
testClient.get()
.uri("/statuses/no-content")
.exchange()
.expectStatus()
.isNoContent();
}
@Test
public void whenCallRest_thenStatusIsAccepted() {
testClient.get()
.uri("/statuses/accepted")
.exchange()
.expectStatus()
.isAccepted();
}
@Test
public void whenCallRest_thenStatusIsBadRequest() {
testClient.get()
.uri("/statuses/bad-request")
.exchange()
.expectStatus()
.isBadRequest();
}
@Test
public void whenCallRest_thenStatusIsUnauthorized() {
testClient.get()
.uri("/statuses/unauthorized")
.exchange()
.expectStatus()
.isUnauthorized();
}
@Test
public void whenCallRest_thenStatusIsNotFound() {
testClient.get()
.uri("/statuses/not-found")
.exchange()
.expectStatus()
.isNotFound();
}
}
@@ -0,0 +1,92 @@
package com.baeldung.spring.rsocket.client;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
import com.baeldung.spring.rsocket.model.MarketData;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketRequester.RequestSpec;
import org.springframework.messaging.rsocket.RSocketRequester.ResponseSpec;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@RunWith(SpringRunner.class)
@WebFluxTest(value = MarketDataRestController.class)
public class MarketDataRestControllerIntegrationTest {
@Autowired
private WebTestClient testClient;
@MockBean
private RSocketRequester rSocketRequester;
@Mock
private RequestSpec requestSpec;
@Mock
private ResponseSpec responseSpec;
@Test
public void whenInitiatesRequest_ThenGetsResponse() throws Exception {
when(rSocketRequester.route("currentMarketData")).thenReturn(requestSpec);
when(requestSpec.data(any())).thenReturn(responseSpec);
MarketData marketData = new MarketData("X", 1);
when(responseSpec.retrieveMono(MarketData.class)).thenReturn(Mono.just(marketData));
testClient.get()
.uri("/current/{stock}", "X")
.exchange()
.expectStatus()
.isOk()
.expectBody(MarketData.class)
.isEqualTo(marketData);
}
@Test
public void whenInitiatesFireAndForget_ThenGetsNoResponse() throws Exception {
when(rSocketRequester.route("collectMarketData")).thenReturn(requestSpec);
when(requestSpec.data(any())).thenReturn(responseSpec);
when(responseSpec.send()).thenReturn(Mono.empty());
testClient.get()
.uri("/collect")
.exchange()
.expectStatus()
.isOk()
.expectBody(Void.class);
}
@Test
public void whenInitiatesRequest_ThenGetsStream() throws Exception {
when(rSocketRequester.route("feedMarketData")).thenReturn(requestSpec);
when(requestSpec.data(any())).thenReturn(responseSpec);
MarketData firstMarketData = new MarketData("X", 1);
MarketData secondMarketData = new MarketData("X", 2);
when(responseSpec.retrieveFlux(MarketData.class)).thenReturn(Flux.just(firstMarketData, secondMarketData));
FluxExchangeResult<MarketData> result = testClient.get()
.uri("/feed/{stock}", "X")
.accept(TEXT_EVENT_STREAM)
.exchange()
.expectStatus()
.isOk()
.returnResult(MarketData.class);
Flux<MarketData> marketDataFlux = result.getResponseBody();
StepVerifier.create(marketDataFlux)
.expectNext(firstMarketData)
.expectNext(secondMarketData)
.thenCancel()
.verify();
}
}
@@ -0,0 +1,98 @@
package com.baeldung.spring.rsocket.server;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import com.baeldung.spring.rsocket.model.MarketData;
import com.baeldung.spring.rsocket.model.MarketDataRequest;
import io.rsocket.RSocket;
import io.rsocket.RSocketFactory;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.transport.netty.client.TcpClientTransport;
import java.time.Duration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.MimeTypeUtils;
@RunWith(SpringRunner.class)
@SpringBootTest
@TestPropertySource(properties = {"spring.rsocket.server.port=7000"})
public class MarketDataRSocketControllerLiveTest {
@Autowired
private RSocketRequester rSocketRequester;
@SpyBean
private MarketDataRSocketController rSocketController;
@Test
public void whenGetsFireAndForget_ThenReturnsNoResponse() throws InterruptedException {
final MarketData marketData = new MarketData("X", 1);
rSocketRequester.route("collectMarketData")
.data(marketData)
.send()
.block(Duration.ofSeconds(10));
sleepForProcessing();
verify(rSocketController).collectMarketData(any());
}
@Test
public void whenGetsRequest_ThenReturnsResponse() throws InterruptedException {
final MarketDataRequest marketDataRequest = new MarketDataRequest("X");
rSocketRequester.route("currentMarketData")
.data(marketDataRequest)
.send()
.block(Duration.ofSeconds(10));
sleepForProcessing();
verify(rSocketController).currentMarketData(any());
}
@Test
public void whenGetsRequest_ThenReturnsStream() throws InterruptedException {
final MarketDataRequest marketDataRequest = new MarketDataRequest("X");
rSocketRequester.route("feedMarketData")
.data(marketDataRequest)
.send()
.block(Duration.ofSeconds(10));
sleepForProcessing();
verify(rSocketController).feedMarketData(any());
}
private void sleepForProcessing() throws InterruptedException {
Thread.sleep(1000);
}
@TestConfiguration
public static class ClientConfiguration {
@Bean
@Lazy
public RSocket rSocket() {
return RSocketFactory.connect()
.mimeType(MimeTypeUtils.APPLICATION_JSON_VALUE, MimeTypeUtils.APPLICATION_JSON_VALUE)
.frameDecoder(PayloadDecoder.ZERO_COPY)
.transport(TcpClientTransport.create(7000))
.start()
.block();
}
@Bean
@Lazy
RSocketRequester rSocketRequester(RSocketStrategies rSocketStrategies) {
return RSocketRequester.wrap(rSocket(), MimeTypeUtils.APPLICATION_JSON, rSocketStrategies);
}
}
}
@@ -0,0 +1,41 @@
package com.baeldung.spring.serverconfig;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
@RunWith(SpringRunner.class)
@WebFluxTest
public class GreetingControllerIntegrationTest {
@Autowired
private WebTestClient webClient;
@MockBean
private GreetingService greetingService;
private final String name = "Baeldung";
@Before
public void setUp() {
when(greetingService.greet(name)).thenReturn(Mono.just("Greeting Baeldung"));
}
@Test
public void shouldGreet() {
webClient.get().uri("/greet/{name}", name)
.exchange()
.expectStatus()
.isOk()
.expectBody(String.class)
.isEqualTo("Greeting Baeldung");
}
}
@@ -0,0 +1,57 @@
package com.baeldung.spring.serverconfig;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import javax.net.ssl.SSLException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec;
import reactor.netty.http.client.HttpClient;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
@DirtiesContext
public class GreetingLiveTest {
private static final String BASE_URL = "https://localhost:8443";
private WebTestClient webTestClient;
@Before
public void setup() throws SSLException {
webTestClient = WebTestClient.bindToServer(getConnector())
.baseUrl(BASE_URL)
.build();
}
@Test
public void shouldGreet() {
final String name = "Baeldung";
ResponseSpec response = webTestClient.get()
.uri("/greet/{name}", name)
.exchange();
response.expectStatus()
.isOk()
.expectBody(String.class)
.isEqualTo("Greeting Baeldung");
}
private ReactorClientHttpConnector getConnector() throws SSLException {
SslContext sslContext = SslContextBuilder
.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
HttpClient httpClient = HttpClient.create().secure(t -> t.sslContext(sslContext));
return new ReactorClientHttpConnector(httpClient);
}
}
@@ -0,0 +1,8 @@
package com.baeldung.spring.serverconfig;
import org.springframework.test.context.ActiveProfiles;
@ActiveProfiles("skipAutoConfig")
public class GreetingSkipAutoConfigLiveTest extends GreetingLiveTest {
}
@@ -0,0 +1,176 @@
package com.baeldung.spring.webclientrequests;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import java.time.Duration;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.DefaultUriBuilderFactory;
import reactor.core.publisher.Mono;
@RunWith(SpringRunner.class)
@WebFluxTest
public class WebClientRequestsUnitTest {
private static final String BASE_URL = "https://example.com";
private WebClient webClient;
@Captor
private ArgumentCaptor<ClientRequest> argumentCaptor;
private ExchangeFunction exchangeFunction;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
this.exchangeFunction = mock(ExchangeFunction.class);
ClientResponse mockResponse = mock(ClientResponse.class);
when(this.exchangeFunction.exchange(this.argumentCaptor.capture())).thenReturn(Mono.just(mockResponse));
this.webClient = WebClient
.builder()
.baseUrl(BASE_URL)
.exchangeFunction(exchangeFunction)
.build();
}
@Test
public void whenCallSimpleURI_thenURIMatched() {
this.webClient.get()
.uri("/products")
.exchange()
.block(Duration.ofSeconds(1));
verifyCalledUrl("/products");
}
@Test
public void whenCallSinglePathSegmentUri_thenURIMatched() {
this.webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/{id}")
.build(2))
.exchange()
.block(Duration.ofSeconds(1));
verifyCalledUrl("/products/2");
}
@Test
public void whenCallMultiplePathSegmentsUri_thenURIMatched() {
this.webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/{id}/attributes/{attributeId}")
.build(2, 13))
.exchange()
.block(Duration.ofSeconds(1));
verifyCalledUrl("/products/2/attributes/13");
}
@Test
public void whenCallSingleQueryParams_thenURIMatched() {
this.webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/")
.queryParam("name", "AndroidPhone")
.queryParam("color", "black")
.queryParam("deliveryDate", "13/04/2019")
.build())
.exchange()
.block(Duration.ofSeconds(1));
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");
}
@Test
public void whenCallSingleQueryParamsPlaceholders_thenURIMatched() {
this.webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/")
.queryParam("name", "{title}")
.queryParam("color", "{authorId}")
.queryParam("deliveryDate", "{date}")
.build("AndroidPhone", "black", "13/04/2019"))
.exchange()
.block(Duration.ofSeconds(1));
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13%2F04%2F2019");
}
@Test
public void whenCallArrayQueryParamsBrackets_thenURIMatched() {
this.webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/")
.queryParam("tag[]", "Snapdragon", "NFC")
.build())
.exchange()
.block(Duration.ofSeconds(1));
verifyCalledUrl("/products/?tag%5B%5D=Snapdragon&tag%5B%5D=NFC");
}
@Test
public void whenCallArrayQueryParams_thenURIMatched() {
this.webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/")
.queryParam("category", "Phones", "Tablets")
.build())
.exchange()
.block(Duration.ofSeconds(1));
verifyCalledUrl("/products/?category=Phones&category=Tablets");
}
@Test
public void whenCallArrayQueryParamsComma_thenURIMatched() {
this.webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/")
.queryParam("category", String.join(",", "Phones", "Tablets"))
.build())
.exchange()
.block(Duration.ofSeconds(1));
verifyCalledUrl("/products/?category=Phones,Tablets");
}
@Test
public void whenUriComponentEncoding_thenQueryParamsNotEscaped() {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT);
this.webClient = WebClient
.builder()
.uriBuilderFactory(factory)
.baseUrl(BASE_URL)
.exchangeFunction(exchangeFunction)
.build();
this.webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/")
.queryParam("name", "AndroidPhone")
.queryParam("color", "black")
.queryParam("deliveryDate", "13/04/2019")
.build())
.exchange()
.block(Duration.ofSeconds(1));
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");
}
private void verifyCalledUrl(String relativeUrl) {
ClientRequest request = this.argumentCaptor.getValue();
Assert.assertEquals(String.format("%s%s", BASE_URL, relativeUrl), request.url().toString());
Mockito.verify(this.exchangeFunction).exchange(request);
verifyNoMoreInteractions(this.exchangeFunction);
}
}
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{1.}): %msg%n%throwable
</Pattern>
</layout>
</appender>
<root level="info">
<appender-ref ref="Console"/>
</root>
</configuration>