Camel: Mock a processor to test Route - java

I am trying to create a unit test for the routing.
I have the following route configuration
from ("direct:getA")
.routeId("get-a").startupOrder(1)
.process(exchange -> {
QueryObject queryObject = exchange.getIn().getBody(QueryObject.class);
exchange.getIn().setHeader(foo, queryObject.getH());
exchange.setOut(exchange.getIn());
})
.choice()
.when(header(foo).isEqualTo(fooConstant.bar))
.process("barProcessor")
.when(header(foo).isEqualTo(fooConstant.bie))
.process("bieProcessor")
.end();
My question is, how can I mock "barProcessor" and "bieProcessor"?
I tried to use adviceWith but I could not retrieve the routeDefinition. The context.getRouteDefinitions() returns an empty list.
Edit:
Below is the code snippet from my test.
RouteDefinition routeDef = context.getRouteDefinition("get-a");
routeDef.adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
interceptSendToEndpoint("*barProcessor*").process(
new Processor() {
#Override
public void process(Exchange exchange) {
System.out.println("bar");
}
}
);
interceptSendToEndpoint("*bieProcessor*").process(
new Processor() {
#Override
public void process(Exchange exchange) {
System.out.println("Bie");
}
}
);
}
});
String request = <JSON Request>;
websocket.sendTest(request);
But the context.getRouteDefinition("get-a"); is returning null. And when I also used context.getRouteDefinitions(), it returns an empty list.

I found the cause of this issue.
I forgot to Override the createRouteBuilder and createJndiContext.
#Override
protected RouteBuilder createRouteBuilder() {
return new SampleRoute(<param>);
}
#Override
protected JndiContext createJndiContext() throws Exception {
JndiContext context = new JndiContext();
context.bind("barProcessor", new BarProcessor());
context.bind("bieProcessor", new BieProcessor());
return context;
}
I found the solution from this link: Unit Test

Related

Transition Handlers defined using #OnTransition stop working when statemachine is rehydrated using new StateMachineContext

In the following sample code, I created a state machine and rehydrated it with the state of my choice. However, when I send events, the handlers defined with #OnTransition do not run.
If I comment method-call to #rehydrateState, then the handlers work appropriately. Why is this happening?
Also: if I replace the line
var sm = factory.getStateMachine("Machine1"); with
var sm = factory.getStateMachine("");
Then all handlers run fine. Why is that the case?
#Configuration
#EnableStateMachineFactory
public class Config extends StateMachineConfigurerAdapter<String, String> {
#Override
public void configure(StateMachineStateConfigurer<String, String> states)
throws Exception {
var allStates = new HashSet<String>();
allStates.add("UNPAID");
allStates.add("WAITING_FOR_RECEIVE");
allStates.add("DONE");
states
.withStates()
.initial("UNPAID")
.states(allStates);
}
#Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
throws Exception {
transitions
.withExternal()
.source("UNPAID").target("WAITING_FOR_RECEIVE")
.event("PAY")
.and()
.withExternal()
.source("WAITING_FOR_RECEIVE").target("DONE")
.event("RECEIVE")
.and()
.withExternal()
.source("WAITING_FOR_RECEIVE").target("WAITING_FOR_RECEIVE")
.event("SELF");
}
#Override
public void configure(StateMachineConfigurationConfigurer<String, String> config)
throws Exception {
config
.withConfiguration()
.autoStartup(true);
}
#WithStateMachine
static class Action {
#OnTransition(target = "UNPAID")
public void create() {
System.out.println("UNPAID");
}
#OnTransition(source = "UNPAID", target = "WAITING_FOR_RECEIVE")
public void pay() {
System.out.println("WAITING_FOR_RECEIVE");
}
#OnTransition(source = "WAITING_FOR_RECEIVE", target = "WAITING_FOR_RECEIVE")
public void self() {
System.out.println("self works too");
}
#OnTransition(source = "WAITING_FOR_RECEIVE", target = "DONE")
public void receive() {
System.out.println("DONE");
}
}
}
public class Main implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
#Autowired
StateMachineFactory factory;
#Override
public void run(String... args) throws Exception {
var sm = factory.getStateMachine("Machine1");
sm.stopReactively().block();
rehydrateState(sm, sm.getExtendedState(), "WAITING_FOR_RECEIVE");
sm.startReactively().block();
sm.sendEvent("PAY");
sm.sendEvent("SELF");
}
void rehydrateState(StateMachine<String, String> sm, ExtendedState extendedState, String status) {
sm.getStateMachineAccessor().doWithAllRegions(sma -> {
sma.resetStateMachineReactively(new DefaultStateMachineContext<>(status, null, null, extendedState)).block();
});
}
}
Replaced #resetStateMachineReactively method call by passing machine Id and all handlers started to work.
sma.resetStateMachineReactively(new DefaultStateMachineContext<>(status, null, null, extendedState, null, sm.getId())).block();

Apache camel process(processor) method is not being called

I saw in another post how manually adding the camel context and starting it should work, but it hasn't for me. I double checked the from, and to paths and they seem to be correct. Not sure why it's not calling the method and would appreciate some advice
public class CsvRouteBuilder extends DdsRouteBuilder {
private CsvConverterProcessor csvConverterProcessor;
private CamelContext camelContext;
#Autowired
public CsvRouteBuilder(CsvConverterProcessor csvConverterProcessor) throws Exception {
this.csvConverterProcessor = csvConverterProcessor;
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
from("{{input.files.csv}}")
.routeId("CSVConverter")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println("hitting");
}
})
.to("{{output.files.csv}}");
}
});
camelContext.start();
}
The processor is not called simply because your route is not properly declared such that Spring Boot is not aware of it.
The proper way is to make your class extend RouteBuilder to define your route(s) and annotate your class with #Component to mark it as candidate for auto-detection when using annotation-based configuration and classpath scanning.
Your code should rather be something like this:
#Component
public class CsvRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
from("{{input.files.csv}}")
.routeId("CSVConverter")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println("hitting");
}
})
.to("{{output.files.csv}}");
}
}

How to dynamically add headers from map when defining a RouteBuilder

I'm creating a RouteBuilder for a test:
final RouteBuilder routeBuilder = new RouteBuilder() {
#Override
public void configure() throws Exception {
from(start).routeId(id).
process(myProcessor).
to(end);
}
};
camelContext.addRoutes(routeBuilder);
and I need to also set some headers coming from a Map<String, String>. In other words I'd like to do something like:
headers.entrySet().
forEach(header -> {
setHeader(header.getKey(), constant(header.getValue()));
});
but that setHeader should be related to the RouteDefinition that I was creating inside the #configure method of the RouteBuilder.
How can I do it?
Thanks
Typing from my mobile. Thus format may not be correct:
final RouteBuilder routeBuilder = new RouteBuilder() {
#Override
public void configure() throws Exception {
RouteDefinition route = from(start).routeId(id);
headers.entrySet().
forEach(header -> {
route = route.setHeader(header.getKey(),
constant(header.getValue()));
});
route.process(myProcessor).
to(end);
}
};
camelContext.addRoutes(routeBuilder);`

Object result in Apache Camel junit test

I trying to test the camel output as Object but which fails to get exchange object.This is where it fails Customer resultCustomer = processActs.getExchanges().get(0).getIn().getBody(Customer.class). Please help me to solve this.I referred this Right way to test my object in Camel
Customer POJO:
public class Customer {
private String firstName;
private String lastName;
// getters and setters
#Override
public String toString(){
return firstName +":::" + lastName;
}
}
Test Route:
public class FileTest4 extends CamelTestSupport {
#EndpointInject(uri = "direct:teststart")
private Endpoint start;
#EndpointInject(uri = "mock:direct:processActs")
private MockEndpoint processActs;
#EndpointInject(uri = "mock:direct:write2File")
private MockEndpoint write2File;
#EndpointInject(uri = "mock:end")
private MockEndpoint mockEndResult;
#Override
public boolean isUseAdviceWith() {
return true;
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("file:/var/file.log&noop=true").routeId("MY_ROUTE").to("direct:processActs");
from("direct:processActs").process(exchange -> {
List<Customer> customers = new ArrayList<>();
customers.add(new Customer("F1", "L1"));
customers.add(new Customer("F2", "L2"));
customers.add(new Customer("F3", "L3"));
exchange.getOut().setBody(customers);
}).to("direct:write2File");
from("direct:write2File").split(simple("${body}")).log("Content: ${body}");
}
};
}
#Override
protected void doPostSetup() throws Exception {
context.getRouteDefinition("MY_ROUTE").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceFromWith("direct:teststart");
weaveAddLast().to("mock:end");
}
});
context.start();
}
#Test
public void testUnmarshal() throws Exception {
mockEndResult.expectedMessageCount(1);
// ArrayIndex Exception here exchanges list is empty
Customer resultCustomer = processActs.getExchanges().get(0).getIn().getBody(Customer.class);
assertEquals(resultCustomer.toString(),"F1:::L1");
write2File.expectedBodiesReceived("F1:::L1", "F3:::L3", "F2:::L2");
template.sendBody("direct:teststart", new File("src/test/resources/test.txt"));
mockEndResult.assertIsSatisfied();
}
}
It looks like you're inspecting the mock endpoint before you've actually sent any exchanges. Try moving the check to the end of the test, e.g.:
#Test
public void testUnmarshal() throws Exception {
mockEndResult.expectedMessageCount(1);
write2File.expectedBodiesReceived("F1:::L1", "F3:::L3", "F2:::L2");
template.sendBody("direct:teststart", new File("src/test/resources/test.txt"));
mockEndResult.assertIsSatisfied();
Customer resultCustomer = processActs.getExchanges().get(0).getIn().getBody(Customer.class);
assertEquals(resultCustomer.toString(),"F1:::L1");
}
UPDATE
On closer inspection, I think you've got your mocks muddled up. Judging by the assertions you want to check that three customers are written out. However your mocks aren't set up for this.
mock:end is added to the end of MY_ROUTE but that will only ever see the entire customer list returned by the processor in direct:processActs
Also the mocks you declare with #EndpointInject don't get involved in the route because you don't actually mock the real endpoints. You can remove all of them apart from mockEndResult.
The following test does pass.
#Test
public void testUnmarshal() throws Exception {
mockEndResult.expectedMessageCount(1);
template.sendBody("direct:teststart", new File("src/test/resources/test.txt"));
mockEndResult.assertIsSatisfied();
#SuppressWarnings("unchecked")
List<Customer> customers = mockEndResult.getExchanges().get(0).getIn().getBody(List.class);
assertEquals(customers.get(0).toString(), "F1:::L1");
assertEquals(customers.get(1).toString(), "F2:::L2");
assertEquals(customers.get(2).toString(), "F3:::L3");
}
That might not be what you want to test, though. Instead you could weave the mock endpoint in to the splitter, then you'd be able to assert individual customers.
#Override
protected void doPostSetup() throws Exception {
context.getRouteDefinition("MY_ROUTE").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceFromWith("direct:teststart");
}
});
// give direct:write2File the id 'splitter' to be able to advice it
context.getRouteDefinition("splitter").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
weaveByType(LogDefinition.class).after().to("mock:end");
}
});
context.start();
}
#Test
public void testUnmarshal() throws Exception {
mockEndResult.expectedMessageCount(3);
mockEndResult.expectedBodiesReceived("F1:::L1", "F2:::L2", "F3:::L3");
template.sendBody("direct:teststart", new File("src/test/resources/test.txt"));
mockEndResult.assertIsSatisfied();
}

How to test multiple RouteBuilders in Apache Camel

I want to test multiple camel RouteBuilder in a single unit test
what I have:
Custom camel processor that changes state
public class MyProcessor implements Processor {
MyState state;
public MyProcessor(MyState state) {this.state = state;}
#Override
public void process(Exchange exchange) throws Exception {
state.setState(state.getState() + 5);
}}
Two simple RouteBuilders: first routes messages from "direct:start" to "direct:endroute1" second pick up messages from "direct:endroute1" and routes somewhere "mock:endroute2"
public class MyRouteBuilder1 extends RouteBuilder {
MyState state;
public MyRouteBuilder1(MyState state) {this.state = state;}
#Override
public void configure() throws Exception {
from("direct:start").process(new MyProcessor(state)).to("direct:endroute1");
}}
public class MyRouteBuilder2 extends RouteBuilder {
MyState state;
public MyRouteBuilder2(MyState state) {this.state = state;}
#Override
public void configure() throws Exception {
from("direct:endroute1").process(new MyProcessor(state)).to("mock:endroute2");
}}
Writing unit test for a single route builder is straightforward:
public class MyTest extends CamelTestSupport {
MyState state = new MyStateImpl();
#EndpointInject(uri = "mock:result")
protected MockEndpoint resultEndpoint;
#Test
public void testSingleRoute() throws Exception {
resultEndpoint.expectedMessageCount(1);
template.sendBody("direct:start", new Object());
assertTrue(state.getState() == 5);
resultEndpoint.assertIsSatisfied();
}
#Override
protected RouteBuilder createRouteBuilder() {
return new MyRouteBuilder1(state) {
public void configure() throws Exception{
super.configure();
from("direct:endroute1").to("mock:result");
}
};
}
}
What I really want to do is somehow to override CamelTestSupport.createRouteBuilder that will test whole chain of message processing from direct:start to mock:endroute2. As a result state.getState() should be 10
Try to override method:
protected RouteBuilder[] createRouteBuilders() {...}
from CamelTestSupport. It's available since version 2.17
you can just add multiple RouteBuilders to the context using the context.addRoutes(RouteBuilder) API
see this unit test for an example:
https://svn.apache.org/repos/asf/camel/trunk/camel-core/src/test/java/org/apache/camel/builder/AddRoutesAtRuntimeTest.java
You could use one RouteBuilder including the routes of multiple other RouteBuilders.

Categories