Apache camel 2.16 enrich - No consumers available on endpoint in JUnit - java

I upgraded to camel 2.16 and one of my route Unit Tests started failing.
Here is my route definition:
public class Route extends RouteBuilder{
#Override
public void configure() throws Exception {
from(start).enrich("second");
from("direct:second")
.log(LoggingLevel.DEBUG, "foo", "Route [direct:second] started.");
}
}
Here is my test:
#RunWith(MockitoJUnitRunner.class)
public class RouteTest extends CamelTestSupport {
private Route builder;
#Produce(uri = "direct:start")
protected ProducerTemplate template;
#Before
public void config() {
BasicConfigurator.configure();
}
#Override
protected RouteBuilder createRouteBuilder() {
builder = new Route();
return builder;
}
#Override
protected CamelContext createCamelContext() throws Exception {
SimpleRegistry registry = new SimpleRegistry();
return new DefaultCamelContext(registry);
}
#Test
public void testPrimeRouteForSubscriptionId() {
Exchange exchange = ExchangeBuilder.anExchange(new DefaultCamelContext()).build();
exchange.getIn().setBody(new String("test"));
template.send(exchange);
}
}
The error I'm getting when I run the test is:
org.apache.camel.component.direct.DirectConsumerNotAvailableException: No consumers available on endpoint: Endpoint[direct://second]. Exchange[][Message: test]
Worthy of note is the following line in the camel 2.16 notes:
http://camel.apache.org/camel-2160-release.html
The resourceUri and resourceRef attributes on and has been removed as they now support a dynamic uris computed from an Expression.
Thanks in advance for any help.

Swap the order so the the direct route is started before the enrich.
http://camel.apache.org/configuring-route-startup-ordering-and-autostartup.html
Or use seda instead of direct in your unit test: http://camel.apache.org/seda
Or use ?block=true in the direct uri to tell Camel to block and wait for a consumer to be started and ready before it sends a message to it: http://camel.apache.org/direct

This is a somewhat old issue, but since i pulled out most of my hair out last night, trying to figure out why it was ok to use to("direct:myEndpoint") but not enrich("direct:myEndpoint"), I'll post the answer anyway - maybe it'll save somebody else from getting bald spots ;-)
It turns out to be a test-issue. In case of Direct endpoints, enrich checks whether there is a running route in the context before passing the Exchange to it, but it does so by looking at the CamelContext held by the Exchange it is currently handling. Since you passed your ProducerTemplate an Exchange what was created with a new DefaultCamelContext(), it has no "direct:second" route available.
Luckily there is a couple of simple solutions. Either create the Exchange using the CamelContext from CamelTestSupport, or use the ProducerTemplate sendBody(...) method instead:
#Test
public void testWithSendBody() {
template.sendBody(new String("test"));
}
#Test
public void testPrimeRouteForSubscriptionId() {
Exchange exchange = ExchangeBuilder.anExchange(context()).build();
exchange.getIn().setBody(new String("test"));
template.send(exchange);
}

The blueprint test keeps throwing exception, No Consumers available.
My scenario was that I have an osgi svc which exposes a method which can be called from any another osgi svc.
So the exposed svc method makes a call to a direct:
#EndpointInject(uri = "direct-vm:toRestCall")
ProducerTemplate toRestCall;
svcMethod(Exchange xch){
exchange.setOut(
toRestCall.send("seda:toDirectCall", xch -> {
try{
xch.getIn().setBody("abc");
}catch (Exception ex){
ex.getMessage();
}
}
}).getIn());
And when I tested the direct that it calls, Blueprint advice with JUnit used to keep throwing the following exception:
org.apache.camel.component.direct.DirectConsumerNotAvailableException:
No consumers available on endpoint: Endpoint. Exchange[Message: {..........

Related

Apache Camel | Spring Testing | Intercept Route is not working

I am new to apache camel so I am still struggling to write camel test cases.
I have below route defined
from("direct:routeToTest")
.id(ROUTE_ID)
.to(LOOK_UP_ROUTE)
.choice()
.when(some-condition)
.choice()
.when(condition)
.to(CREATE_ROUTE)
.otherwise().process(exchange -> exchange.getIn().setBody(prepareResponse(""))
.endChoice()
.otherwise()
.log("Some Issue")
.process(exchange -> unknownError(exchange))
.endChoice();
}
while testing I am trying to intercept to defined in my route and set some mock response to it. So after some search I found using adviceWith is the right way to achieve it.
So my test is like below. The outcome of the test is, it is still going to Look_up_route (direct:lookUpRoute, another route defined) for processing with the data passed but the expectation is code should skip this to and set response as "MockResponse"
#SpringBootTest
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
#RunWith(CamelSpringBootRunner.class)
#UseAdviceWith
#MockEndpoints
#DisableJmx(false)
public class RouteTest {
#Autowired
private ProducerTemplate producerTemplate;
#Autowired
private CamelContext context;
#Test
public void testResponseToJSON() throws Exception {
SomeObject someObject = getObject();
context.getRouteDefinition(ROUTE_ID).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
interceptSendToEndpoint(LOOK_UP_ROUTE)
.skipSendToOriginalEndpoint()
.transform("MockOutput");
}
);
context.start();
Object object = producerTemplate.requestBody(direct:routeToTest, someObject);
}
}
I'd like to know how do I skip .to(LOOK_UP_ROUTE) and set mockResponse to it.
Your intercept statement looks good at first sight. However, have a look at this answer for an alternative approach.
You have to
Add an id to the LOOK_UP_ROUTE step in your route
Then you can use adviceWith to remove or manipulate the marked step in your tests
Set the message body (and header) to whatever it should be for your test

Spring Cloud Stream Application Exits Before Supplier Finishes

I have a "task" application that is short lived and produces messages to Kafka based on statuses from a database. I'm using spring cloud stream to produce the messages using the below format of my application. I followed this format from the Spring Cloud Stream documentation to send arbitrary data to the output binding.
private EmitterProcessor<Message<GenericRecord>> processor;
#Override
public void run(ApplicationArguments arg0) {
// ... create Message<GenericRecord> producerRecord
this.processor.onNext(producerRecord);
}
#Bean
public Supplier<Flux<Message<GenericRecord>>> supplier() {
return () -> this.processor;
}
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args);
ctx.close();
}
The application runs, creates the records, runs onNext(), and then exits. I then look to see if any messages have been published but there are none on the topic. I then added a Thread.sleep(10000) after each message is produced and the messages end up on the topic.
After looking at the documentation for Reactor I didn't seen any clear ways to accomplish this. Is there a way to wait for the EmitterProcessor to finish publishing the messages before the Spring application exits?
Do you have a specific reason to use the EmittorProcessor? I think this use-case can be solved by using StreamBridge. For e.g.
#Autowired StreamBridge streamBridge;
#Override
public void run(ApplicationArguments arg0) {
// ... create Message<GenericRecord> producerRecord
this.streamBridge.send("process-out-0", producerRecord);
}
Then provide configuration for: spring.cloud.stream.source: process
You can find more details on StreamBridge in the ref docs.

Issue testing spring cloud SQS Listener

Environment
Spring Boot: 1.5.13.RELEASE
Cloud: Edgware.SR3
Cloud AWS: 1.2.2.RELEASE
Java 8
OSX 10.13.4
Problem
I am trying to write an integration test for SQS.
I have a local running localstack docker container with SQS running on TCP/4576
In my test code I define an SQS client with the endpoint set to local 4576 and can successfully connect and create a queue, send a message and delete a queue. I can also use the SQS client to receive messages and pick up the message that I sent.
My problem is that if I remove the code that is manually receiving the message in order to allow another component to get the message nothing seems to be happening. I have a spring component annotated as follows:
Listener
#Component
public class MyListener {
#SqsListener(value = "my_queue", deletionPolicy = ON_SUCCESS)
public void receive(final MyMsg msg) {
System.out.println("GOT THE MESSAGE: "+ msg.toString());
}
}
Test
#RunWith(SpringRunner.class)
#SpringBootTest(properties = "spring.profiles.active=test")
public class MyTest {
#Autowired
private AmazonSQSAsync amazonSQS;
#Autowired
private SimpleMessageListenerContainer container;
private String queueUrl;
#Before
public void setUp() {
queueUrl = amazonSQS.createQueue("my_queue").getQueueUrl();
}
#After
public void tearDown() {
amazonSQS.deleteQueue(queueUrl);
}
#Test
public void name() throws InterruptedException {
amazonSQS.sendMessage(new SendMessageRequest(queueUrl, "hello"));
System.out.println("isRunning:" + container.isRunning());
System.out.println("isActive:" + container.isActive());
System.out.println("isRunningOnQueue:" + container.isRunning("my_queue"));
Thread.sleep(30_000);
System.out.println("GOT MESSAGE: " + amazonSQS.receiveMessage(queueUrl).getMessages().size());
}
#TestConfiguration
#EnableSqs
public static class SQSConfiguration {
#Primary
#Bean(destroyMethod = "shutdown")
public AmazonSQSAsync amazonSQS() {
final AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration("http://127.0.0.1:4576", "eu-west-1");
return new AmazonSQSBufferedAsyncClient(AmazonSQSAsyncClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("key", "secret")))
.withEndpointConfiguration(endpoint)
.build());
}
}
}
In the test logs I see:
o.s.c.a.m.listener.QueueMessageHandler : 1 message handler methods found on class MyListener: {public void MyListener.receive(MyMsg)=org.springframework.cloud.aws.messaging.listener.QueueMessageHandler$MappingInformation#1cd4082a}
2018-05-31 22:50:39.582 INFO 16329 ---
o.s.c.a.m.listener.QueueMessageHandler : Mapped "org.springframework.cloud.aws.messaging.listener.QueueMessageHandler$MappingInformation#1cd4082a" onto public void MyListener.receive(MyMsg)
Followed by:
isRunning:true
isActive:true
isRunningOnQueue:false
GOT MESSAGE: 1
This demonstrates that in the 30 second pause between sending the message the container didn't pick it up and when I manually poll for the message it is there on the queue and I can consume it.
My question is, why isn't the listener being invoked and why is the isRunningOnQueue:false line suggesting that it's not auto started for that queue?
Note that I also tried setting my own SimpleMessageListenerContainer bean with autostart set to true explicitly (the default anyway) and observed no change in behaviour. I thought that the org.springframework.cloud.aws.messaging.config.annotation.SqsConfiguration#simpleMessageListenerContainer that is set up by #EnableSqs ought to configure an auto started SimpleMessageListenerContainer that should be polling for me message.
I have also set
logging.level.org.apache.http=DEBUG
logging.level.org.springframework.cloud=DEBUG
in my test properties and can see the HTTP calls create the queue, send a message and delete etc but no HTTP calls to receive (apart from my manual one at the end of the test).
I figured this out after some tinkering.
Even if the simple message container factory is set to not auto start, it seems to do its initialisation anyway, which involves determining whether the queue exists.
In this case, the queue is created in my test in the setup method - but sadly this is after the spring context is set up which means that an exception occurs.
I fixed this by simply moving the queue creation to the context creation of the SQS client (which happens before the message container is created). i.e.:
#Bean(destroyMethod = "shutdown")
public AmazonSQSAsync amazonSQS() {
final AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration("http://localhost:4576", "eu-west-1");
final AmazonSQSBufferedAsyncClient client = new AmazonSQSBufferedAsyncClient(AmazonSQSAsyncClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("dummyKey", "dummySecret")))
.withEndpointConfiguration(endpoint)
.build());
client.createQueue("test-queue");
return client;
}

how do I Mock endpoints with blueprint in camel?

I'm writing an application using camel for deployment (eventually) in a fuse container. The nature of the project requires that I mix and match Java and XML DSL.
I'm having trouble getting the mock framework to work properly with blueprint.
Here's my unit test, based completely on the example here.
public class MockNotWorking extends CamelBlueprintTestSupport {
#Test
public void testAdvisedMockEndpointsWithPattern() throws Exception {
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
mockEndpoints("log*");
}
});
getMockEndpoint("mock:log:foo").expectedBodiesReceived("Bye World");
getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
template.sendBody("direct:start", "Hello World");
// additional test to ensure correct endpoints in registry
assertNotNull(context.hasEndpoint("direct:start"));
assertNotNull(context.hasEndpoint("log:foo"));
assertNotNull(context.hasEndpoint("mock:result"));
// only the log:foo endpoint was mocked
assertNotNull(context.hasEndpoint("mock:log:foo"));
assertNull(context.hasEndpoint("mock:direct:start"));
assertNull(context.hasEndpoint("mock:direct:foo"));
assertMockEndpointsSatisfied();
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start").to("direct:foo").to("log:foo").to("mock:result");
from("direct:foo").transform(constant("Bye World"));
}
};
}
protected String getBlueprintDescriptor() {
return "OSGI-INF/blueprint/blueprint.xml";
}
}
I have copied verbatim the example here, and modified it very slightly so we extend CamelBlueprintTestSupport instead of CamelTestSupport. This requires over-riding getBlueprintDescriptor to point to my blueprint xml, in which I have defined one very basic (and completely irrelevant to the test) route:
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<camelContext id="validationRoute" xmlns="http://camel.apache.org/schema/blueprint" >
<route id="validation">
<from uri="direct:validation" />
<log message="validating..." />
</route>
</camelContext>
</blueprint>
The test fails with:
java.lang.AssertionError: mock://log:foo Received message count. Expected: <1> but was: <0>
So this means the message did not reach the mock endpoint. Change CamelBlueprintTestSupport for CamelTestSupport and it works.
So how do I get mocks like this working correctly with blueprint?
When you use blueprint, it imports all the routes you have defined in the blueprint xml file(s), and adds them to the CamelContext.
The reason this breaks things is that the context.getRouteDefinitions().get(0) no longer refers to the only route - there are now more than one. So when you add the AdviceWithRouteBuilder, it could be added to the wrong route.
The following code fixes the problem (and will work for non-blueprint tests too):
List<RouteDefinition> routes = context.getRouteDefinitions();
for (int i=0; i<routes.size(); i++) {
context.getRouteDefinitions().get(i).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
// mock all endpoints
mockEndpoints("log*");
}
});
}
======update======
A better way of doing this is rather than mocking all routes, find our specific route by id and then apply the advice. This means first setting the route id:
from("direct:start").routeId("start").to("direct:foo").to("log:foo").to("mock:result");
And then looking up the route by id and calling adviceWith as before:
context.getRouteDefinition("start").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
// mock log endpoints
mockEndpoints("log*");
}
});
Actually it depends on what version of Camel you are using, some methods doesn't work as expected. Its improved a lot better after 2.12 if I remember right. (2.15 is way better)
After all my encounters with Camel unit testing, I ended up documenting all stuffs here:
http://bushorn.com/unit-testing-apache-camel/
http://bushorn.com/camel-unit-testing-using-mock-endpoint/
By the way, instead of this "mockEndpoints("log*");", I would do this:
weaveByToString(".direct:foo.").after().to("mock:log"); (This sounds weird, I know ;) )
or if you can set an endpoint id
weaveById("endpoint-id-of-direct-foo").after().to("mock:log");
or
weaveAddLast().to("mock:log");

Apache Camel tesing. Remove RoutePolicy

Here is Apache Camel route:
ZooKeeperRoutePolicy routePolicy = new ZooKeeperRoutePolicy("zookeeper:localhost:2181/fuse-example/routePolicy", 1);
from("file:camelInpit").routeId("systemARoute")
.routePolicy(routePolicy)
.log(LoggingLevel.ERROR, "Starting route")
[...]
I want to remove routePolicy in my tests since there is no ZooKeeper in test environment, but this is not as easy as it seems
context.getRouteDefinition("systemARoute").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceFromWith("direct:aaa");
weaveByType(RouteDefinition.class).selectIndex(1).remove();
}
});
weaveById("policy") and setting the id routePolicy(...).id("policy") does not help.
How can I dynamically remove RoutePolicies while testing?
Is it not possible to do something like that?
from("file:camelInpit").routeId("systemARoute")
.choice()
.when(prodEnvironmentExpression)
.routePolicy(routePolicy)
.endChoice()
.end()
.log(LoggingLevel.ERROR, "Starting route")
You can access the original route and set its route policies to null
context.getRouteDefinition("systemARoute").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
getOriginalRoute().setRoutePolicies(null);
}
});
But we should maybe add fluent DSL builders for this to make it stand out?
If you bind it to the context you can easily make a mock of the policy in your tests by using, where myPolicy is a mock or a policy that does nothing.
Even easier if you make an abstract MyCamelTestSupport which overides that and then all your tests which needs to mock it extends MyCamelTestSupport
#Override
protected JndiRegistry createRegistry() throws Exception {
JndiRegistry jndi = super.createRegistry();
jndi.bind("myPolicy", myPolicy);
return jndi;
}

Categories