Working of routing functions (reactive-webflux) with spring cloud contract - spring

I am building a spring webflux project in which I have implemented Routing function as controllers with spring customer driven contract.
I am facing issue while the tests cases are getting executed and I haven't found any solution regarding this online anywhere.
My requirement is to execute the generated tests and loading the application context for n no. of tests.Below is the sample code:
==========Base class===========
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class GetBase extends SampleParentBase{
protected MockMvc mvc ;
#MockBean
private PersonService service;
#MockBean
private ServerRequest request;
#Before
public void setup() throws Exception {
mvc = MockMvcBuilders
.webAppContextSetup(context).build();
}
}
============groovy file==================
Contract.make {
description "."
request {
method GET()
urlPath('/person/1')
headers {
contentType(applicationJson())
header('''Accept''', applicationJson())
}
}
response {
headers {
contentType(applicationJson())
}
status 200
bodyMatchers {
jsonPath('$.name', byType())
jsonPath('$.age', byType())
jsonPath('$.pId', byType())
}
body ('''{
"name":"string",
"age":20,
"pId":"string"
}''')
}
}
=======================Router Configuration====================
#Configuration
public class RoutesConfiguration {
#Autowired
PersonRespository personRespository;
#Bean
RouterFunction<?> routes() {
return nest(path("/person"),
route(RequestPredicates.GET("/{id}"),
request -> ok().body(personRespository.findById(request.pathVariable("id")), Person.class))
.andRoute(method(HttpMethod.GET),
request -> ok().body(personRespository.findAll(), Person.class))
);
}
}

We've updated the snapshot documentation to contain information on how to work with Web Flux. You can check it out here https://cloud.spring.io/spring-cloud-contract/2.0.x/single/spring-cloud-contract.html#_working_with_web_flux and here you have a sample with Web Flux https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/master/producer_webflux . Let me copy the part of the documentation for your convenience
Spring Cloud Contract requires the usage of EXPLICIT mode in your generated tests to work with Web Flux.
Maven.
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<configuration>
<testMode>EXPLICIT</testMode>
</configuration>
</plugin>
Gradle.
contracts {
testMode = 'EXPLICIT'
}
The following example shows how to set up a base class and Rest Assured for Web Flux:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = BeerRestBase.Config.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = "server.port=0")
public abstract class BeerRestBase {
// your tests go here
// in this config class you define all controllers and mocked services
#Configuration
#EnableAutoConfiguration
static class Config {
#Bean
PersonCheckingService personCheckingService() {
return personToCheck -> personToCheck.age >= 20;
}
#Bean
ProducerController producerController() {
return new ProducerController(personCheckingService());
}
}
}

Related

Feign Client Custom Configuration Does Not Take Effect

I use feign in my project. I created a custom configuration but it is overridden by default configuration. Here are the steps:
SpringBootApplication.java
#SpringBootApplication
#EnableDiscoveryClient
#EnableFeignClients
public class MyApplication {}
FeignConfiguration.java
#RequiredArgsConstructor
public class FeignConfiguration{
private final MyConfigurationProperties myConfigProperties;
#Bean
public MetaDataRestClient metaDataRestClient(#Qualifier("metaDataHttpClient") okhttp3.OkHttpClient metaDataHttpClient) {
return Feign.builder()
.retryer(Retryer.NEVER_RETRY)
.client(new OkHttpClient(metaDataHttpClient))
.encoder(new JacksonEncoder(XML_MAPPER))
.decoder(new JacksonDecoder(XML_MAPPER))
.contract(new SpringMvcContract())
.logger(new Slf4jLogger(MetaDataRestClient.class))
.logLevel(Logger.Level.FULL)
.target(MetaDataRestClient.class, myConfigProperties.getMetadata().getEndpoint());
}
#Primary
#Bean(name = "metaDataHttpClient")
public okhttp3.OkHttpClient metaDataHttpClientWithProxy() {
return OkHttpUtil.createNewHttpClientBuilderWithProxy(myConfigProperties.getMetadata().getFeignClient().getConnectTimeout(),
myConfigProperties.getMetadata().getFeignClient().getReadTimeout()).build();
}
MetaDataRestClient.java
#FeignClient(name = "metaDataRestClient", url = "https://myurl.net", configuration = FeignConfiguration.class)
public interface MetaDataRestClient {
#Headers("Content-Type: text/xml")
#GetMapping("/metadata")
public EntityDescriptor getMetadaData();
}
I see that the metaDataRestClient bean is triggered on startup, but when I dig into feign library code, I see that this method is triggered twice: first time with my custom OkHttpClient, and second time with somehing called org.springframework.cloud.sleuth.instrument.web.client.feign.LazyClient. So my custom OkHttpClient is overridden by this lazy client.
Here is the related Feign library code that is being triggered twice:
FeignBuilder.java
public Builder client(Client client) {
this.client = client;
return this;
}
I do not have any feign configurationn in my application.yaml file. What could be the reason for that?
Thanks.

Spring Boot Injecting Implementations for Prod and Test

I'm new to spring boot and I'm trying to wrap my head around how to make dependency injection work for deployment and testing.
I have a #RestController and a supporting #Service. The service injects another class that is an interface for talking to Kafka. For the Kafka interface I have two implementations: one real and one fake. The real one I want to use in production and the fake in test.
My approach is to use two different configuration for each environment (prod and test).
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
Then in my main application I would like to somehow load AppConfiguration.
#SpringBootApplication
public class DeployerServiceApiApplication {
public static void main(String[] args) {
SpringApplication.run(DeployerServiceApiApplication.class, args);
}
// TODO: somehow load here...
}
And in my test load the fake configuration somehow
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest {
#Autowired private MockMvc mockMvc;
// TODO: somehow load AppTestConfiguration here
#Test
public void testDeployAction() throws Exception {
...
ResultActions resultActions = mockMvc.perform(...);
...
}
}
I've spent the better part of a day trying to figure this out. What I'm trying to accomplish here is fundamental and should be straight forward yet I keep running into issues which makes me wonder if the way I'm thinking about this is all wrong.
Am not sure if i understand your question completely but from description i guess you wish to initialize bean based on environment. Please see below.
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
and then you can pass the "-Dspring.profiles.active=prod" argument while starting you application using java command or you can also specify the profile in your test case like below.
#SpringBootTest
#ActiveProfile("test")
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest
Use spring profiles, you can annotate your test class with #ActiveProfiles("test-kafka") and your test configuration with #Profile("test-kafka").
This is pretty easy task in spring boot world
Rewrite your classes as follows:
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
This will instruct spring boot to load the relevant configuration when the "prod"/"test" specified.
Then you can start your application in production with --spring.profiles.active=prod and in the Test you can write something like this:
#SpringBootTest
#ActiveProfiles("test")
public class DeployerServiceApiApplicationTest {
...
}
If you want to run all the tests with this profile and do not want to write this ActiveProfiles annotation you can create src/test/resources/application.properties and put into it: spring.active.profiles=test

SpringBoot2 + Webflux - WebTestClient always returns “401 UNAUTHORIZED”

I am trying to write some test using WebTestClient under Springboot 2.1.8 and Junit5
It's always returning < 401 UNAUTHORIZED Unauthorized, but actually it didn't go to the controller or service layer at all. It may related to spring security, just my guess.
The project was generated using JHipster. Here is the build.gradle
-----------------UimApiServiceImplTest.java-------------------
...
#ExtendWith(SpringExtension.class)
#WebFluxTest(controllers = UserGuidController.class)
#ContextConfiguration(classes = {UserGuidController.class, UimApiServiceImpl.class})
public class UimApiServiceImplTest {
#Autowired
private WebTestClient webTestClient;
#Test
public void testGetGuidByEmail() {
webTestClient.get()
.uri("/uimapi/getguid/{email}", "someone#xxxxx.com")
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.expectStatus().isOk();
}
}
--------------------UserGuidController.java--------------------
...
#RestController
#RequestMapping("/uimapi")
public class UserGuidController {
#Autowired
private UimApiServiceImpl uimApiService;
private static final Logger logger = LoggerFactory.getLogger(UserGuidController.class);
#GetMapping("/getguid/{email}")
public String getUserGuid(#PathVariable String email) {
return uimApiService.getUserGuid(email);
}
}
For webflux, you can disable SecurityAutoconfiguration by excluding the ReactiveSecurityAutoConfiguration class like this:
#WebFluxTest(controllers = YourController.class,excludeAutoConfiguration = {ReactiveSecurityAutoConfiguration.class}))
You've implementation "org.springframework.boot:spring-boot-starter-security" in your gradle dependencies. Spring boot automatically enables security on all endpoints by default when this dependency is found in classpath.
You can choose to disable this default configuration by updating your main class:
#SpringBootApplication(exclude = { SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class })
This would disable security on all endpoints.
If you want to have control of which endpoints to remove security from, you can do that using the below code with updates matching your requirements:
#Configuration
public class SecurityConfiguration {
#Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange().anyExchange().permitAll();
return http.build();
}
}

spring boot tests and embedded elastic server

The documentation says that writing #SpringbootTest doesnt mean we load all the configuration
We should be able to test a slice of application at a time
I have a case where one module talks to elastic search and we spin up EmbeddedElasticsearchServer in integration tests
However I have hard time figuring out how to define #Configuration classes, how to load them in test and how to make sure that elasticsearch server spins up once for all the tests
I am not sure about the #SpringBootTest. But you can use the below template for elasticsearch Integration est with embeddedserver
v7.0.0
#ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.SUITE, numDataNodes = 1, numClientNodes = 0, transportClientRatio = 0, supportsDedicatedMasters = false)
#RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
public class TestWatsonBulkIT extends ESIntegTestCase {
#Override
protected boolean addMockHttpTransport() {
return false;
}
#Override
protected Settings nodeSettings(int nodeOrdinal) {
Settings.Builder builder = Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(NetworkModule.TRANSPORT_TYPE_KEY, Netty4Plugin.NETTY_HTTP_TRANSPORT_NAME)
.put(NetworkModule.HTTP_TYPE_KEY, Netty4Plugin.NETTY_HTTP_TRANSPORT_NAME)
.put(HttpTransportSettings.SETTING_HTTP_PORT.getKey(), 9200)
.put(HttpTransportSettings.SETTING_HTTP_HOST.getKey(),"127.0.0.1");
Settings settings = builder.build();
return settings;
}
#Before
public void setUp() throws Exception {
beforeClass();
super.setUp();
}
#Test
public void test_1(){ // your integration test code here }
}
We should be able to test a slice of application at a time
This is possible by providing classes in #SpringBootTest annotation. I usually go for this option in order to not load everything. Let's say,
Controller:
SampleController -> ConsumerService (autowired)
Test
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {ConsumerService.class, SampleController.class})
public class TestSample {
#Autowired
SampleController sam;
#Test
public void testSam() {
sam.sample();
}
}
I do have bunch of services but they won't be loaded and so their dependencies autowired.
If this is not what you're looking for please enhance your question by adding some sample codes.

How to specify my restful API for swagger-ui in spring-boot application

I am using spring-boot + jersey as restful implementation. I have setup the swagger and I am able to open the swagger ui on the browser. But the swagger-ui doesn't have any API to show, it is an empty page. Below is the code I setup for configuring swagger. How can I let swagger to scan my API definition in jersey?
SwaggerConfiguration.java
#Configuration
#EnableSwagger2
public class SwaggerConfiguration {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/com.hello.*"))
.build().pathMapping("/swagger2");
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("App API")
.description("App API")
.version("1.0.0-SNAPSHOT")
.termsOfServiceUrl("")
.contact("Cooltoo company")
.license("Public")
.licenseUrl("http://hello.com/")
.build();
}
JerseyConfiguration.java
#Configuration
#EnableSwagger2
#EnableAutoConfiguration
#Api(value = "home", description = "Demo API")
#ApplicationPath("/nursego")
public class JerseyConfiguration extends ResourceConfig {
public JerseyConfiguration() {
register(BadgeAPI.class);
register(MultiPartFeature.class);
register(OrderAPI.class);
register(NurseAPI.class);
configureSwagger();
}
private void configureSwagger() {
BeanConfig beanConfig = new BeanConfig();
beanConfig.setVersion("1.0.2");
beanConfig.setSchemes(new String[]{"http"});
beanConfig.setHost("localhost:8080");
beanConfig.setBasePath("/nursego");
beanConfig.setResourcePackage("com.cooltoo.backend.api");
beanConfig.setPrettyPrint(true);
beanConfig.setScan(true);
register( io.swagger.jaxrs.listing.ApiListingResource.class );
register( io.swagger.jaxrs.listing.SwaggerSerializers.class );
}
}
When I open http://localhost:8080/swagger-ui.html, I see below image but none of them are from my API. I don't know where they are from
I used the BeanConfig class to embed the Swagger into my SpringBoot+Jersey implmentation, the code example is as follows,
#Component
#ApplicationPath( "/api" )
public class JerseyConfig extends ResourceConfig{
public JerseyConfig(){
// method for embedding the Swagger
configSwagger();
// registers the REST resource classes
configEndPoints();
}
private void configEndPoints(){
// here register all the REST resource classes
}
private void configSwagger(){
BeanConfig beanConfig = new BeanConfig();
beanConfig.setSchemes( new String[]{ "http" } );
beanConfig.setHost( "localhost:9001" );
beanConfig.setBasePath( "/api" );
beanConfig.setDescription( "REST API services for accessing the pcg application" );
beanConfig.setTitle( "RESTAPI" );
beanConfig.setVersion( "1.0.1" );
// this will tell Swagger config to scan only these packages
beanConfig.setResourcePackage( "com.aig.rest.web" );
beanConfig.setScan( true );
register( io.swagger.jaxrs.listing.ApiListingResource.class );
register( io.swagger.jaxrs.listing.SwaggerSerializers.class );
}
}
I believe #EnableSwagger2 annotation works if the endpoints are implemented using Spring MVC instead of Jersey (or any other JAX-RS impl).
I have detailed how to accomplish this in a blog post I created earlier this year, Microservices using Spring Boot, Jersey Swagger and Docker
Basically if you need to document your Jersey-implemented endpoints via Swagger, you would need to:
1)
Make sure your Spring Boot app scans for components located in specific packages (ie com.asimio.jerseyexample.config) via:
#SpringBootApplication(
scanBasePackages = {
"com.asimio.jerseyexample.config", "com.asimio.jerseyexample.rest"
}
)
2) Jersey configuration class implementation:
package com.asimio.jerseyexample.config;
...
#Component
public class JerseyConfig extends ResourceConfig {
#Value("${spring.jersey.application-path:/}")
private String apiPath;
public JerseyConfig() {
// Register endpoints, providers, ...
this.registerEndpoints();
}
#PostConstruct
public void init() {
// Register components where DI is needed
this.configureSwagger();
}
private void registerEndpoints() {
this.register(HelloResource.class);
// Access through /<Jersey's servlet path>/application.wadl
this.register(WadlResource.class);
}
private void configureSwagger() {
// Available at localhost:port/swagger.json
this.register(ApiListingResource.class);
this.register(SwaggerSerializers.class);
BeanConfig config = new BeanConfig();
config.setConfigId("springboot-jersey-swagger-docker-example");
config.setTitle("Spring Boot + Jersey + Swagger + Docker Example");
config.setVersion("v1");
config.setContact("Orlando L Otero");
config.setSchemes(new String[] { "http", "https" });
config.setBasePath(this.apiPath);
config.setResourcePackage("com.asimio.jerseyexample.rest.v1");
config.setPrettyPrint(true);
config.setScan(true);
}
}
3) Resource implementation using JAX-RS (Jersey) and Swagger annotations:
package com.asimio.jerseyexample.rest.v1;
...
#Component
#Path("/")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#Api(value = "Hello resource", produces = "application/json")
public class HelloResource {
private static final Logger LOGGER = LoggerFactory.getLogger(HelloResource.class);
#GET
#Path("v1/hello/{name}")
#ApiOperation(value = "Gets a hello resource. Version 1 - (version in URL)", response = Hello.class)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Hello resource found"),
#ApiResponse(code = 404, message = "Hello resource not found")
})
public Response getHelloVersionInUrl(#ApiParam #PathParam("name") String name) {
LOGGER.info("getHelloVersionInUrl() v1");
return this.getHello(name, "Version 1 - passed in URL");
}
...
}
4) Make sure your app's Spring Boot configuration file makes a distinction between Spring MVC (for actuator endpoints) and Jersey (for resources) endpoints:
application.yml
...
# Spring MVC dispatcher servlet path. Needs to be different than Jersey's to enable/disable Actuator endpoints access (/info, /health, ...)
server.servlet-path: /
# Jersey dispatcher servlet
spring.jersey.application-path: /api
...

Resources