I have a spring boot application with routes defined as follows:
#Component
public class SampleRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
rest("/customers-controller/")
.get("/customers").to("direct:getcustomer)
.get("/{id}").to("direct:customerDetail")
.get("/{id}/orders").to("direct:customerOrders")
.post("/neworder").to("direct:customerNewOrder");
}
I'm trying to create a contract to test this endpoint ('/customers') and create a stub that will be used in my consumer class.
A contract for messaging services like camel is similar to this:
Contract.make {
label("positive")
input {
messageFrom("seda:getcustomer")
messageBody([
id: "25_body"
])
messageHeaders {
messagingContentType(applicationJson())
// header("id","123_header")
}
}
outputMessage {
sentTo("seda:iris-address-int")
body([
"id":"25_body","firstname":null,"lastname":null,"email":null,"phone":null,"street":null,"city":null,"postcode":null
]
)
headers {
messagingContentType()
}
}
}
Now, I'm not sure on how to define the contract such that it points to my chosen rest endpoint like I would do with a RestController.
Consider the test below. Is it possible to generate this test on the provider side using spring cloud contract given that I'm not using the #RestController but rather the rest component ?
#RunWith(CamelSpringBootRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class TestRestRoute {
#Autowired
private TestRestTemplate restTemplate;
#LocalServerPort
int randomServerPort
#Test
public void test_bank_route() throws URISyntaxException, IOException {
//call the REST API
final String baseUrl = "http://localhost:" + randomServerPort + "/customers-controller/customers";
URI uri = new URI(baseUrl);
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class );
Assert.assertEquals(MediaType.APPLICATION_JSON_VALUE, Objects.requireNonNull(response.getHeaders().getContentType()).toString());
Assert.assertEquals(200, response.getStatusCodeValue());
Assert.assertNull(response.getBody());
}
With this commit https://github.com/spring-cloud-samples/spring-cloud-contract-samples/commit/760d7105bde2f253ca0bff84fa3f4d5a35a1931e I've added a sample for Camel to spring cloud contract branch 3.0.x. Of course same rules apply to Spring Cloud Contract in other versions. In general what you can do is on the producer side:
Define a configuration for a route and extract the component URIs to separate methods:
#Configuration
class RouteConfiguration {
#Bean
RoutesBuilder myRouter() {
return new RouteBuilder() {
#Override
public void configure() {
from(start())
.bean(MyProcessor.class)
.to(finish());
}
};
}
// rabbitmq://hostname[:port]/exchangeName?[options]
String start() { return "rabbitmq:localhost/person?queue=person"; }
String finish() {
return "rabbitmq:localhost/verifications?queue=verifications";
}
}
However in the contract we will leverage the seda component (the way you did it)
Contract.make {
label("positive")
input {
messageFrom("seda:person")
messageBody([
age: 25
])
messageHeaders {
messagingContentType(applicationJson())
}
}
outputMessage {
sentTo("seda:verifications")
body([
eligible: true
])
headers {
messagingContentType(applicationJson())
}
}
}
Now, in the base class for the generated tests we will change the configuration to reflect what we have in the contract
package com.example.demo;
import org.apache.camel.test.spring.CamelSpringRunner;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.verifier.messaging.boot.AutoConfigureMessageVerifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
#RunWith(CamelSpringRunner.class)
#SpringBootTest(classes = BaseClass.TestConfiguration.class)
// IMPORTANT
#AutoConfigureMessageVerifier
// IMPORTANT
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public abstract class BaseClass {
#Configuration
#EnableAutoConfiguration
static class TestConfiguration extends RouteConfiguration {
// was: rabbit
// will be: a queue
#Override
String start() {
return "seda:person";
}
#Override
String finish() {
return "seda:verifications";
}
}
}
We're overriding the start() and finish() methods. You could do sth similar in your case. Then on the consumer side you can reference it like this:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.camel.CamelContext;
import org.apache.camel.ConsumerTemplate;
import org.apache.camel.ProducerTemplate;
import org.assertj.core.api.BDDAssertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
import org.springframework.test.annotation.DirtiesContext;
#SpringBootTest
#AutoConfigureStubRunner(
ids = "com.example:beer-api-producer-camel",
stubsMode = StubRunnerProperties.StubsMode.LOCAL
)
// IMPORTANT
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class DemoApplicationTests {
#Autowired ConsumerTemplate consumerTemplate;
#Autowired ProducerTemplate producerTemplate;
#Autowired CamelContext camelContext;
ObjectMapper objectMapper = new ObjectMapper();
// consumer -> seda:person
// producers -> seda:person -> person -> verifications -> seda:verifications
// consumer -> seda:verifications
#BeforeEach
public void setup() {
this.camelContext.getShutdownStrategy().setTimeout(1);
}
#Test
public void should_trigger_a_negative_verification() throws Exception {
this.producerTemplate.sendBodyAndHeader("seda:person", new Person(17),
"contentType", "application/json");
String string =
this.consumerTemplate.receiveBody("seda:verifications", String.class);
Verification verification = this.objectMapper.readerFor(Verification.class).readValue(string);
BDDAssertions.then(verification).isNotNull();
BDDAssertions.then(verification.eligible).isFalse();
}
#Test
public void should_trigger_a_positive_verification() throws Exception {
this.producerTemplate.sendBodyAndHeader("seda:person", new Person(25),
"contentType", "application/json");
String string =
this.consumerTemplate.receiveBody("seda:verifications", String.class);
Verification verification = this.objectMapper.readerFor(Verification.class).readValue(string);
BDDAssertions.then(verification).isNotNull();
BDDAssertions.then(verification.eligible).isTrue();
}
}
Related
I have this scenario. I have many queues in Azure ServiceBus, the implementation below works, however it is not flexible since would need to replicate for each queue. I would like to change from dynamically forming, maybe as a parameter in the method send() the queue name and the OUTPUT_CHANNEL for #ServiceActivator and #MessagingGateway, It is possible?
import com.azure.spring.cloud.service.servicebus.properties.ServiceBusEntityType;
import com.azure.spring.integration.core.handler.DefaultMessageHandler;
import com.azure.spring.messaging.servicebus.core.ServiceBusTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.MessageHandler;
import org.springframework.stereotype.Component;
#Component
public class TestIntegration {
private static final String OUTPUT_CHANNEL = "output";
private static final String QUEUE_NAME = "myQueue";
#Autowired
private QueueOutboundGateway messagingGateway;
public void send(String message) {
this.messagingGateway.send(message);
}
#Bean
#ServiceActivator(inputChannel = OUTPUT_CHANNEL)
public MessageHandler queueMessageSender(ServiceBusTemplate serviceBusTemplate) {
serviceBusTemplate.setDefaultEntityType(ServiceBusEntityType.QUEUE);
return new DefaultMessageHandler(QUEUE_NAME, serviceBusTemplate);
}
#MessagingGateway(defaultRequestChannel = OUTPUT_CHANNEL)
public interface QueueOutboundGateway {
void send(String text);
}
}
The com.azure.spring.integration.core.handler.DefaultMessageHandler supports a dynamic destination resolution from message headers:
private String toDestination(Message<?> message) {
if (message.getHeaders().containsKey(AzureHeaders.NAME)) {
return message.getHeaders().get(AzureHeaders.NAME, String.class);
}
return this.destination;
}
So, what you need is a #Header(name = AzureHeaders.NAME) String destination argument on your gateway's send() method. There is no reason in the dynamic nature for the OUTPUT_CHANNEL: only one gateway and one service activator for that DefaultMessageHandler is enough. You call send() with payload and target destination as params.
I'm just trying use Camel Reactive Stream together with Spring Boot Reactor using the following code
package com.manning.camel.reactive;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.reactive.streams.api.CamelReactiveStreamsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
/**
* A simple Camel route that triggers from a timer and calls a bean and prints to system out.
* <p/>
* Use <tt>#Component</tt> to make Camel auto-detect this route when starting.
*/
#RestController
public class MySpringBootRouter extends RouteBuilder {
#Autowired
private ProducerTemplate template;
#Autowired
private CamelReactiveStreamsService crss;
#GetMapping
public Mono<String> sayHi() {
template.asyncSendBody("direct:works", "Hi");
//return Mono.from(crss.fromStream("greet", String.class));
return Mono.from(crss.fromStream("greet", String.class));
}
#Override
public void configure() {
from("direct:works")
.log("Fired")
.to("reactive-streams:greet");
}
}
After run the code
java.lang.IllegalStateException: The stream has no active subscriptions
After a long time, solved the error, as can be noticed the Router Class logic was changed a little
#Slf4j
#Service
#AllArgsConstructor
public class MyService {
final CamelContext context;
#PostConstruct
public void consumerData() {
var rCamel = CamelReactiveStreams.get(context);
var numbers = rCamel.fromStream("numbers", Integer.class);
Flux.from(numbers).subscribe(e -> log.info("{}", e));
}
}
#Component
#NoArgsConstructor
public class MyRouter extends RouteBuilder {
// Injects the Subscriber
#Autowired MyService service;
#Override
public void configure() {
//onException(ReactiveStreamsNoActiveSubscriptionsException.class)
// .continued(true);
from("timer://reactiveApp?fixedRate=true&period=2s")
.transform(method(Random.class, "nextInt(100)"))
//.log("${body}");
.to("direct:message");
from("direct:message")
//.log("${body}")
.to("reactive-streams:numbers");
}
}
Thid is my mapping from my controller class , now I want to write unit test case for the same
#GetMapping(value = "/tokenSearch/{id}/{name}/{values}/{data_name}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> getValuesfromToken(
throws ClientProtocolException, IOException, ParseException {
ResponseEntity<String> data = elasticService.getData();
return data;
}
this is what I was trying but its asking for castargument for Result matcher, getting error , can someone pls help me with this
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class ElasticCaseTests extends Mockito {
#Autowired
private MockMvc mockMvc;
#Test
public void testGetValuesfromToken() throws Exception {
String contentAsString = mockMvc.perform(get("/tokenSearch/1/PRODUCT/PRODUCT/189")).andExpect(status().isOk())
.andExpect(jsonPath("$.id", is("1"))).andExpect(jsonPath("$.name", is("product")))
.andExpect(jsonPath("$.values", is("product")))
.andExpect(jsonPath("$.searching_word", is("189"))).andExpect(status().isOk()).andReturn().getResponse()
.getContentAsString();
}
java.lang.AssertionError: No value at JSON path "$.id"' , can someone help me with this
This could be one example:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
class ControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
void testGetValuesfromToken() throws Exception {
this.mockMvc
.perform(get("/tokenSearch/............."))
.andExpect(status().isOk())
.andExpect(jsonPath("$.hierarchy_name"").value(".....what you expect..."))
.andDo(print());
}
See example it may help
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import ….services.AuthorizationService;
import ….AuthorizationRequest;
import ….AuthorizationResponse;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import org.junit.Before;
import org.junit.Test;
public class MyControllerTest {
AuthorizationService authorizationService;
AuthorizationController controller;
private Clock clock;
#Before
public void setUp() throws Exception {
clock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
authorizationService = mock(AuthorizationService.class);
controller = new AuthorizationController(authorizationService);
}
#Test
public void createJws() throws Exception {
when(authorizationService.createJws(any(AuthorizationRequest.class)))
.thenReturn(new AuthorizationResponse());
AuthorizationRequest authorizationRequest =
AuthorizationRequest.builder().id(“abc”).build();
AuthorizationResponse jwsResponse =
controller.createJws(authorizationRequest);
verify(authorizationService).createJws(authorizationRequest);
}
}
I usually write my Controller tests more or less like this. If you want to test the json output and status correctness, you could implement a helper method like asJsonString and see if everything is proper. Maybe you find this approach helpful.
#WebMvcTest
#ContextConfiguration(classes = {ResourceController.class})
class ResourceControllerTest {
private final String JSON_CONTENT_TYPE = "application/json";
#Autowired
private MockMvc mockMvc;
#MockBean
private ElasticService elasticService;
#Test
public void shouldReturnProperData() throws Exception {
String data = "some returned data"; // format it properly, as elasticService would
YourJsonObject objectToReturn = new YourJsonObject(...);
when(elasticService.getData()).thenReturn(data);
mockMvc.perform(post("/tokenSearch/1/PRODUCT/PRODUCT/189").contentType(JSON_CONTENT_TYPE).content(asJsonString(email)))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(asJsonString(objectToReturn)));
}
}
private String asJsonString(final Object obj) {
try {
return new ObjectMapper().writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e); // delete this exception conversion if you will :)
}
}
}
The most important is though to test if elasticService returns the proper data so you can implement the unit tests there. Here, you can test statuses, paths, and slightly the json outlook, but these are not any kind of amazing tests.
I have a ServiceWebClientInterface.java like this
import reactor.core.publisher.Mono;
public interface ServiceWebClientInterface {
Mono<String> apiCall();
}
MyClass.java
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
public class MyClass extends AbstractGatewayFilterFactory<MyClass.Config> {
private final ServiceWebClientInterface serviceWebClientInterface;
MyClass(final ServiceWebClientInterface serviceWebClientInterface) {
this.serviceWebClientInterface = serviceWebClientInterface;
}
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
return serviceWebClientInterface.apiCall().flatMap(response -> {
if (!"Valid".equals(response)) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
});
};
}
public static class Config {
// Put the configuration properties
}
}
I'm trying to unit test myMethod using StepVerifier, but I am not able to execute statements inside the inner lambda function of myMethod.
MyClassTest.java
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
class MyClassTest {
#Mock
ServiceWebClientInterface mockServiceWebClientInterface;
#Mock
private ServerWebExchange mockServerWebExchange;
#Mock
private GatewayFilterChain mockGatewayFilterChain;
#Mock
private ServerHttpResponse mockServerHttpResponse;
#BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
void test_apply_forValid() {
when(mockServiceWebClientInterface.apiCall()).thenReturn(Mono.just("Valid"));
MyClass.Config config = new MyClass.Config();
MyClass myClass = new MyClass(mockServiceWebClientInterface);
GatewayFilter gatewayFilter = myClass.apply(config);
Mono<Void> response = gatewayFilter.filter(mockServerWebExchange, mockGatewayFilterChain);
StepVerifier.create(response).expectComplete();
verify(mockServiceWebClientInterface).apiCall();
verify(mockGatewayFilterChain).filter(mockServerWebExchange);
}
#Test
void test_apply_forInValid() {
when(mockServiceWebClientInterface.apiCall()).thenReturn(Mono.just("InValid"));
when(mockServerWebExchange.getResponse()).thenReturn(mockServerHttpResponse);
MyClass.Config config = new MyClass.Config();
MyClass myClass = new MyClass(mockServiceWebClientInterface);
GatewayFilter gatewayFilter = myClass.apply(config);
Mono<Void> response = gatewayFilter.filter(mockServerWebExchange, mockGatewayFilterChain);
StepVerifier.create(response).expectComplete();
verify(mockServiceWebClientInterface).apiCall();
verify(mockServerHttpResponse).setStatusCode(eq(HttpStatus.FORBIDDEN));
verify(mockServerHttpResponse).setComplete();
verify(mockGatewayFilterChain, never()).filter(mockServerWebExchange);
}
}
Please find the complete code above, When I run the tests I observe that the inner lambda function does not get invoked using the step verifier.
I guess you want to test the class that implements MyLambda interface.
For sure you inject there serviceWebClientInterface as mentioned on code snippet.
To unit test that class, you should mock the serviceWebClientInterface.apiCall() and verify if it was called. As an addition to your actual code snippet.
You can use Mockito library for that purpose.
create a mock:
given(serviceWebClientInterface).willReturn(Mono.just("some text"));
then verify if it is called:
verify(serviceWebClientInterface).apiCall()
I was able to fix this issue by using
StepVerifier.create(response).verifyComplete();
and mocking chain.filter(exchange);
I'm trying to access the request body from WebFlux's HandlerFunctionFunction but I am getting java.lang.IllegalStateException: Only one connection receive subscriber allowed..
I want to do something similar to below code block
public class ExampleHandlerFilterFunction
implements HandlerFilterFunction<ServerResponse, ServerResponse> {
#Override
public Mono<ServerResponse> filter(ServerRequest serverRequest,
HandlerFunction<ServerResponse> handlerFunction) {
if (serverRequest.pathVariable("name").equalsIgnoreCase("test")) {
return serverRequest.bodyToMono(Player.class)
.doOnNext(loggerService :: logAndDoSomethingElse)
.then(handlerFunction.handle(serverRequest);
}
return handlerFunction.handle(serverRequest);
}
}
I tried serverRequest.bodyToMono(Player.class).cache() too, but did not work.
Update: Adding handler and router functions
Handler Function
#Component
public class PlayerHandler {
#Autowired
private final playerRepository;
public PlayerHandler(PlayerRepository palyerRepository) {
this.palyerRepository = playerRepository;
}
public Mono<ServerResponse> savePlayer(ServerRequest request) {
Mono<String> id = request.bodyToMono(Player.class)
.map(playerRepository::save)
.map(Player::getId);
return ok().body(id, String.class);
}
}
Router function
#Bean
public RouterFunction<ServerResponse> route(PlayerHandler playerHandler) {
return RouterFunctions
.route(POST("/players/"), playerHandler::save)
.filter(new ExampleHandlerFilterFunction());
}
Logger service
public Mono<Void> T logAndDoSomethingElse(T t){
---- auditing business logic----
return loggerRepository.save(asJsonb);
}
Can someone help me? Thanks
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerFilterFunction;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
#Component
public class FundsAuthorizationFilter implements HandlerFilterFunction<ServerResponse, ServerResponse> {
#Override
public Mono<ServerResponse> filter(ServerRequest request, HandlerFunction<ServerResponse> handlerFunction) {
String block = request.bodyToMono(String.class).block();
JSONObject jsonObj = new JSONObject(block);
ServerRequest.Builder newRequestBuilder = ServerRequest.from(request);
newRequestBuilder.body(block);
return handlerFunction.handle(newRequestBuilder.build());
}
}
I found a solution for this, that is clone the serverRequest and set the body to the new request