apache camel route testing - spring-boot

I have written a test case for an apache camel route below,
I believe this only tests message transfer from SEDA send endpoint to mock endpoint, but may not test prism camel route. want to see if how to mock the actual endpoint - if possible.
package com.sams;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.test.junit5.CamelTestSupport;
import org.junit.jupiter.api.Test;
import com.sams.pricing.prism.data.processor.util.Endpoints;
public class CamelRouteTests extends CamelTestSupport {
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
public void configure() {
from(Endpoints.SEDA_SEND_ENDPOINT)
.to("mock:" + Endpoints.SEDA_PROCESS_ENDPOINT);
}
};
}
#Test
public void testRoute() throws Exception {
// Set up the expectations for the mock endpoint
getMockEndpoint("mock:" + Endpoints.SEDA_PROCESS_ENDPOINT).expectedMessageCount(1);
// Send a message to the seda:sendMessage endpoint
template.sendBody("seda:sendMessage", "test message");
// Verify that the mock endpoint expectations are met
assertMockEndpointsSatisfied();
}
}
// SEDA Endpoint Stage Event Driven Architecture
from(Endpoints.SEDA_SEND_ENDPOINT)
.messageHistory()
// Route Name
.routeId(Endpoints.SEDA_SEND_ENDPOINT)
.log("${body}")
// multicast
.multicast()
.parallelProcessing() // create parellel threads
.log("${body}")
// thread pool
.threads()
.executorService(executorService) // specific thread pool
.log("Camel Route Started Message Processing : - ${body}")
// content based routing
.choice()
.when(
CommonUtility
.costIQPredicate) // predicate checking based on the header value to decide the
// route
// .bean(CostIQService.class, "calculatePrice") // // rules engine call
.bean(CostIQPayloadTransformer.class, "payloadTransformer") // payload transformer
// multiple consumer
.to(
Endpoints.SEDA_PROCESS_ENDPOINT, // consumer 1
Endpoints.SEDA_PROCESS_ENDPOINT, // consumer 2
Endpoints.SEDA_PROCESS_ENDPOINT) // consumer 3
.when(CommonUtility.optimizationPredicate)
.bean(OptimizationService.class, "calculatePrice")
.bean(CostIQPayloadTransformer.class, "payloadTransformer")
.to(
Endpoints.SEDA_PROCESS_ENDPOINT,
Endpoints.SEDA_PROCESS_ENDPOINT,
Endpoints.SEDA_PROCESS_ENDPOINT)
.when(CommonUtility.markDownPredicate)
.bean(MarkDownService.class, "calculatePrice")
.bean(CostIQPayloadTransformer.class, "payloadTransformer")
.to(
Endpoints.SEDA_PROCESS_ENDPOINT,
Endpoints.SEDA_PROCESS_ENDPOINT,
Endpoints.SEDA_PROCESS_ENDPOINT)
.when(CommonUtility.pricingPredicate)
.bean(PricingService.class, "calculatePrice")
.bean(CostIQPayloadTransformer.class, "payloadTransformer")
.to(
Endpoints.SEDA_PROCESS_ENDPOINT,
Endpoints.SEDA_PROCESS_ENDPOINT,
Endpoints.SEDA_PROCESS_ENDPOINT)
.log("Final :- ${body}")
.end();
}
Where SEDA_SEND_ENDPOINT = "seda:sendMessage?blockWhenFull=true&concurrentConsumers=100"
and SEDA_PROCESS_ENDPOINT = "seda:processMessage?blockWhenFull=true"
Have tried to look up mocking but could not find a solution as that would not test the actual camel route.
Update - have tried to use the AdviceWith method as described but am getting an error
AdviceWith.adviceWith(context, Endpoints.SEDA_SEND_ENDPOINT, a -> {
a.mockEndpoints(Endpoints.SEDA_SEND_ENDPOINT);
});
am getting this error below
java.lang.IllegalArgumentException: Cannot advice route as there are
no routes at
org.apache.camel.builder.AdviceWith.findRouteDefinition(AdviceWith.java:262)
at org.apache.camel.builder.AdviceWith.adviceWith(AdviceWith.java:74)
at
com.sams.pricing.prism.data.processor.CamelRouteTests1.testRoute(CamelRouteTests1.java:26)
at java.base/

I don't think you need to override createRouteBuilder() in your Test class. But in your test, you do need to tell Camel to mock the endpoints you're interested in:
AdviceWith.adviceWith(context, Endpoints.SEDA_PROCESS_ENDPOINT, a -> {
a.mockEndpoints(Endpoints.SEDA_PROCESS_ENDPOINT));
});
Then. you could do
getMockEndpoint("mock:" + Endpoints.SEDA_PROCESS_ENDPOINT)...
See this about mocking existing endpoints.

Related

How can I test a direct route with MQ?

I have a camel 3 with springboot application that contains this route:
public class SendForRatingRoute extends RouteBuilder
{
public void configure() throws Exception
{
onException(Exception.class)
.handled(true)
.process(exProcessor);
// Queue
String qn = "ratingmq:queue:MY_OUT_QUEUE");
from("direct:sendRate")
.routeId("sendRate")
.bean(MessagingBean.class, "createMessage" )//creates input object
.marshal().xstream()
.log(LoggingLevel.INFO,logger, "~~~ sending to MQ: ${body} " )
.to(qn)
.log(LoggingLevel.INFO,logger, "~~~ Message was sent to MQ successfully ");
} `
}
this route takes an Order object as input, converts it to a java class that matches the Queue message schema (in the MessagingBean) and then mashall that class to XML and send it to an IBM MQ endpoint.
(for simplicity the configuration of the queue is skipped)
I need to write a test case that passes a sample order to this route and see if it actually sends it to the queue correctly. (I want the message to get sent and not mock the sending)
I created a test class like this:
class SendForRatingTest extends CamelTestSupport
{
public void setUp() throws Exception
{
super.setUp();
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new SendForRatingRoute();
}
#Test
void testSendingForRating() throws Exception
{
template.sendBody("direct:sendRate", getSampleOrder());
assertTrue(true); //??
}
}
The method GetSampleOrder() creates the order object with sample data to pass to the route.
I am not sure if I am doing this right (I have never written test case for camel before)
When I try to run the test case using maven:
mvn test -Dtest=SendForRatingTest -X
I am getting this error:
<<< ERROR!
org.junit.platform.commons.PreconditionViolationException: You must register at least one TestTemplateInvocationContextProvider that supports #TestTemplate method [void SendForRatingTest.testSendingForRating() throws java.lang.Exception]
What am I doing wrong?
Also I am not sure how to test that the route was executed successfully. Ultimately I am going to check the queue for a message but also I want the route test to validate it worked without errors. Since I don't return anything in the route, I am not sure what to put in the assertTrue()
Thank you in advance.

Call external stub from grpc test class

I am trying to call the grpc service using stub (proto are present in the different jar file). But when I am trying to call that service I am getting caused by io.grpc.statusruntimeexception unimplemented method not found. The same is working fine in main class but not in test case.
DeviceGroupServiceImplBase deviceService = Mockito.mock(DeviceGroupServiceImplBase.class,
AdditionalAnswers.delegatesTo(new DeviceGroupServiceImplBase() {
}));
public void createInProcessServerAndChannel() throws IOException {
// Generate a unique in-process server name.
String serverName = InProcessServerBuilder.generateName();
// Create a server, add service, start, and register for automatic graceful
// shutdown.
grpcCleanup.register(
InProcessServerBuilder.forName(serverName).directExecutor().addService(deviceService).build().start());
// Create a client channel and register for automatic graceful shutdown.
ManagedChannel channel = grpcCleanup
.register(InProcessChannelBuilder.forName(serverName).directExecutor().build());
// Create a DeviceGroupServiceClient using the in-process channel;
groupStub = DeviceGroupServiceGrpc.newBlockingStub(channel);
}
// Test case code
When("user calls getDevice with valid deviceUUID {string}", (String deviceUUID) -> {
DeviceUuid request = DeviceUuid.newBuilder().setDeviceUuid(deviceUUID).build();
DeviceGroup groupData = groupStub.getDeviceGroupByDeviceUuid(request);
});
you need to implement getDeviceGroupByDeviceUuid, by default it returns unimplemented status. you can verify if it is calling ServerCall#asyncUnimplementedUnaryCall.
DeviceGroupServiceImplBase deviceService =
Mockito.mock(
DeviceGroupServiceImplBase.class,
AdditionalAnswers.delegatesTo(
new DeviceGroupServiceImplBase() {
#Override
public void getDeviceGroupByDeviceUuid(
DeviceUuid request, StreamObserver<DeviceGroup> responseObserver) {
// TODO: implement
}
}));

Dynamic to() in Apache Camel Route

I am writing a demo program using Apache Camel. Out Camel route is being called from a Spring Boot scheduler and it will transfer file from the source directory C:\CamelDemo\inputFolder to the destination directory C:\CamelDemo\outputFolder
The Spring Boot scheduler is as under
#Component
public class Scheduler {
#Autowired
private ProducerTemplate producerTemplate;
#Scheduled(cron = "#{#getCronValue}")
public void scheduleJob() {
System.out.println("Scheduler executing");
String inputEndpoint = "file:C:\\CamelDemo\\inputFolder?noop=true&sendEmptyMessageWhenIdle=true";
String outputEndpoint = "file:C:\\CamelDemo\\outputFolder?autoCreate=false";
Map<String, Object> headerMap = new HashMap<String, Object>();
headerMap.put("inputEndpoint", inputEndpoint);
headerMap.put("outputEndpoint", outputEndpoint);
producerTemplate.sendBodyAndHeaders("direct:transferFile", null, headerMap);
System.out.println("Scheduler complete");
}
}
The Apache Camel route is as under
#Component
public class FileTransferRoute extends RouteBuilder {
#Override
public void configure() {
errorHandler(defaultErrorHandler()
.maximumRedeliveries(3)
.redeliverDelay(1000)
.retryAttemptedLogLevel(LoggingLevel.WARN));
from("direct:transferFile")
.log("Route reached")
.log("Input Endpoint: ${in.headers.inputEndpoint}")
.log("Output Endpoint: ${in.headers.outputEndpoint}")
.pollEnrich().simple("${in.headers.inputEndpoint}")
.recipientList(header("outputEndpoint"));
//.to("file:C:\\CamelDemo\\outputFolder?autoCreate=false")
}
}
When I am commenting out the line for recipientList() and uncommenting the to() i.e. givig static endpoint in to(), the flow is working. But when I am commenting to() and uncommenting recipientList(), it is not working. Please help how to route the message to the dynamic endpoint (outputEndpoint)?
You are using pollEnrich without specifying an AggregationStrategy: in this case, Camel will create a new OUT message from the retrieved resource, without combining it to the original IN message: this means you will lose the headers previously set on the IN message.
See documentation : https://camel.apache.org/manual/latest/enrich-eip.html#_a_little_enrich_example_using_java
strategyRef Refers to an AggregationStrategy to be used to merge the reply from the external service, into a single outgoing message. By default Camel will use the reply from the external service as outgoing message.
A simple solution would be to define a simple AggregationStrategy on your pollEnrich component, which simply copies headers from the IN message to the new OUT message (note that you will then use the original IN message body, but in your case it's not a problem I guess)
from("direct:transferFile")
.log("Route reached")
.log("Input Endpoint: ${in.headers.inputEndpoint}")
.log("Output Endpoint: ${in.headers.outputEndpoint}")
.pollEnrich().simple("${in.headers.inputEndpoint}")
.aggregationStrategy((oldExchange, newExchange) -> {
// Copy all headers from IN message to the new OUT Message
newExchange.getIn().getHeaders().putAll(oldExchange.getIn().getHeaders());
return newExchange;
})
.log("Output Endpoint (after pollEnrich): ${in.headers.outputEndpoint}")
.recipientList(header("outputEndpoint"));
//.to("file:C:\\var\\CamelDemo\\outputFolder?autoCreate=false");

Not able to to filter messages received using condition attribute in Spring Cloud Stream #StreamListener annotation

I am trying to create a event based system for communicating between services using Apache Kafka as Messaging system and Spring Cloud Stream Kafka.
I have written my Receiver class methods as below,
#StreamListener(target = Sink.INPUT, condition = "headers['eventType']=='EmployeeCreatedEvent'")
public void handleEmployeeCreatedEvent(#Payload String payload) {
logger.info("Received EmployeeCreatedEvent: " + payload);
}
This method is specifically to catch for messages or events related to EmployeeCreatedEvent.
#StreamListener(target = Sink.INPUT, condition = "headers['eventType']=='EmployeeTransferredEvent'")
public void handleEmployeeTransferredEvent(#Payload String payload) {
logger.info("Received EmployeeTransferredEvent: " + payload);
}
This method is specifically to catch for messages or events related to EmployeeTransferredEvent.
#StreamListener(target = Sink.INPUT)
public void handleDefaultEvent(#Payload String payload) {
logger.info("Received payload: " + payload);
}
This is the default method.
When I run the application, I am not able to see the methods annoated with condition attribute being called. I only see the handleDefaultEvent method being called.
I am sending a message to this Receiver Application from the Sending/Source App using the below CustomMessageSource class as below,
#Component
#EnableBinding(Source.class)
public class CustomMessageSource {
#Autowired
private Source source;
public void sendMessage(String payload,String eventType) {
Message<String> myMessage = MessageBuilder.withPayload(payload)
.setHeader("eventType", eventType)
.build();
source.output().send(myMessage);
}
}
I am calling the method from my controller in Source App as below,
customMessageSource.sendMessage("Hello","EmployeeCreatedEvent");
The customMessageSource instance is autowired as below,
#Autowired
CustomMessageSource customMessageSource;
Basicaly, I would like to filter messages received by the Sink/Receiver application and handle them accordingly.
For this I have used the #StreamListener annotation with condition attribute to simulate the behaviour of handling different events.
I am using Spring Cloud Stream Chelsea.SR2 version.
Can someone help me in resolving this issue.
It seems like the headers are not propagated. Make sure you include the custom headers in spring.cloud.stream.kafka.binder.headers http://docs.spring.io/autorepo/docs/spring-cloud-stream-docs/Chelsea.SR2/reference/htmlsingle/#_kafka_binder_properties .

How can I send a message on connect event (SockJS, STOMP, Spring)?

I am connection through SockJS over STOMP to my Spring backend. Everything work fine, the configuration works well for all browsers etc. However, I cannot find a way to send an initial message. The scenario would be as follows:
The client connects to the topic
function connect() {
var socket = new SockJS('http://localhost:8080/myEndpoint');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/notify', function(message){
showMessage(JSON.parse(message.body).content);
});
});
}
and the backend config looks more or less like this:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketAppConfig extends AbstractWebSocketMessageBrokerConfigurer {
...
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/myEndpoint").withSockJS();
}
I want to send to the client an automatic reply from the backend (on the connection event) so that I can already provide him with some dataset (e.g. read sth from the db) without the need for him (the client) to send a GET request (or any other). So to sum up, I just want to send him a message on the topic with the SimMessagingTemplate object just after he connected.
Usually I do it the following way, e.g. in a REST controller, when the template is already autowired:
#Autowired
private SimpMessagingTemplate template;
...
template.convertAndSend(TOPIC, new Message("it works!"));
How to achieve this on connect event?
UPDATE
I have managed to make it work. However, I am still a bit confused with the configuration. I will show here 2 configurations how the initial message can be sent:
1) First solution
JS part
stompClient.subscribe('/app/pending', function(message){
showMessage(JSON.parse(message.body).content);
});
stompClient.subscribe('/topic/incoming', function(message){
showMessage(JSON.parse(message.body).content);
});
Java part
#Controller
public class WebSocketBusController {
#SubscribeMapping("/pending")
Configuration
#Override
public void configureMessageBroker(final MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
... and other calls
template.convertAndSend("/topic/incoming", outgoingMessage);
2) Second solution
JS part
stompClient.subscribe('/topic/incoming', function(message){
showMessage(JSON.parse(message.body).content);
})
Java part
#Controller
public class WebSocketBusController {
#SubscribeMapping("/topic/incoming")
Configuration
#Override
public void configureMessageBroker(final MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
// NO APPLICATION PREFIX HERE
}
... and other calls
template.convertAndSend("/topic/incoming", outgoingMessage);
SUMMARY:
The first case uses two subscriptions - this I wanted to avoid and thought this can be managed with one only.
The second one however has no prefix for application. But at least I can have a single subscription to listen on the provided topic as well as send initial message.
If you just want to send a message to the client upon connection, use an appropriate ApplicationListener:
#Component
public class StompConnectedEvent implements ApplicationListener<SessionConnectedEvent> {
private static final Logger log = Logger.getLogger(StompConnectedEvent.class);
#Autowired
private Controller controller;
#Override
public void onApplicationEvent(SessionConnectedEvent event) {
log.debug("Client connected.");
// you can use a controller to send your msg here
}
}
You can't do that on connect, however the #SubscribeMapping does the stuff in that case.
You just need to mark the service method with that annotation and it returns a result to the subscribe function.
From Spring Reference Manual:
An #SubscribeMapping annotation can also be used to map subscription requests to #Controller methods. It is supported on the method level, but can also be combined with a type level #MessageMapping annotation that expresses shared mappings across all message handling methods within the same controller.
By default the return value from an #SubscribeMapping method is sent as a message directly back to the connected client and does not pass through the broker. This is useful for implementing request-reply message interactions; for example, to fetch application data when the application UI is being initialized. Or alternatively an #SubscribeMapping method can be annotated with #SendTo in which case the resulting message is sent to the "brokerChannel" using the specified target destination.
UPDATE
Referring to this example: https://github.com/revelfire/spring4Test how would that be possible to send anything when the line 24 of the index.html is invoked: stompClient.subscribe('/user/queue/socket/responses' ... from the spring controllers?
Well, look like this:
#SubscribeMapping("/queue/socket/responses")
public List<Employee> list() {
return getEmployees();
}
The Stomp client part remains the same.

Resources