Suppose I have Camel route like this:
#Component
public class MyRoute extends RouteBuilder {
private final BrokenBean brokenBean;
public MyRoute(BrokenBean brokenBean) {
this.brokenBean = brokenBean;
}
#Override
public void configure() throws Exception {
from("{{rabbitmq.inbound}}")
.errorHandler(defaultErrorHandler()
.maximumRedeliveries(1)
.redeliveryDelay(1000))
.end()
.onCompletion()
.onCompleteOnly()
.to("direct:success")
.end()
.onCompletion()
.onFailureOnly()
.to("direct:failure").id("failure")
.end()
.routeId("my_route")
.bean(brokenBean, "hello")
.to("direct:success").id("success");
from("direct:success")
.log("Success received");
from("direct:failure")
.log("Failed received");
}
And here is bean logic which is called from this route.
This is just an example.
#Component
public class BrokenBean {
public void hello() {
System.out.println("Hello called");
}
}
As route logic reveals and I tested it manually, if we got the exception
from BrokeBean the message would be routed to direct:failure and it does in runtime.
But in test below:
#RunWith(CamelSpringBootRunner.class)
#ContextConfiguration(classes = MyRouteTest.TestConfig.class)
public class MyRouteTest {
#Produce("direct:inbound")
protected ProducerTemplate directInbound;
#Autowired
SpringCamelContext context;
#MockBean
BrokenBean brokenBeanMock;
#Before
public void setUp() throws Exception {
AdviceWith.adviceWith(context.getRouteDefinition("my_route"),
context,
new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
onException(RuntimeException.class)
.continued(true);
weaveById("failure").replace().to("mock:direct:failure");
weaveById("success").replace().to("mock:direct:success");
}
});
}
#Test
public void testMyRouteFailedSuccessExpected() throws Exception {
MockEndpoint mockEndpoint = context.getEndpoint("mock:direct:failure",
MockEndpoint.class);
doThrow(new RuntimeException("Failed call")).when(brokenBeanMock).hello();
directInbound.sendBody("hello there");
mockEndpoint.expectedMessageCount(1);
mockEndpoint.assertIsSatisfied();
}
#Test
public void testMyRouteSuccessFailedExpected() throws Exception {
MockEndpoint mockEndpoint = context.getEndpoint("mock:direct:success",
MockEndpoint.class);
doThrow(new RuntimeException("Failed call")).when(brokenBeanMock).hello();
directInbound.sendBody("hello there");
mockEndpoint.expectedMessageCount(1);
mockEndpoint.assertIsSatisfied();
}
#Configuration
#Import({MyRoute.class})
public static class TestConfig extends CamelConfiguration {
#Bean
public BridgePropertyPlaceholderConfigurer bridgePropertyPlaceholderConfigurer() {
final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
final BridgePropertyPlaceholderConfigurer configurer = new BridgePropertyPlaceholderConfigurer();
yaml.setResources(new ClassPathResource("application.yml"));
configurer.setOrder(1);
configurer.setIgnoreUnresolvablePlaceholders(true);
configurer.setProperties(yaml.getObject());
return configurer;
}
}
}
I have the opposite result: testMyRouteSuccessFailedExpected succeeded and testMyRouteFailedSuccessExpected failed.
Which is not what I expected. Actually I tried a lot with adviceWith setup but with no luck.
It seems a simple case to check but behaviour look strange to me.
Any help appreciated. Thanks.
You can essentially only use 1 onCompletion in a route. You have 2. However we don't validate this on startup and report a problem.
Camel cannot understand that your 2 on completions would not overlap as one is for success and another for failure. So you need to only use 1 in your route.
Related
I am currently trying to test an existing route with Apache Camel, but I am not sure I am doing it correctly, because I don't fully understand all the concepts behind Camel.
That being said, here is what I would like to do, on the following example route :
public class TestExampleRoute extends SpringRouteBuilder {
/** The Constant ENDPOINT_EDOSSIER_IMPORT. direct:edossierImport */
public static final String ENDPOINT_EXAMPLE = "direct:testExampleEndpoint";
#Override
public void configure() throws Exception {
// #formatter:off
from(ENDPOINT_EXAMPLE).routeId("testExample")
.bean(TestExampleProcessor.class, "getImportDocumentProcess").id("getImportDocumentProcess")
.bean(TestExampleProcessor.class, "createImportDocumentTraitement").id("createImportDocumentTraitement")
.to(BaseEndpoint.LOG_MESSAGE_SHOW_ALL_MULTILINE);
// #formatter:on
}
}
The point here is just to fetch an ImportDocumentProcess and create an ImportDocumentTraitement that depends on the previous object. The ImportDocumentProcess is passes through the exchange.
Here is the processor code :
#Component("testExampleProcessor")
public class TestExampleProcessor {
/** The Constant LOGGER. */
private static final Logger LOGGER = LogManager.getLogger(TestExampleProcessor.class);
#Autowired
ImportDocumentTraitementService importDocumentTraitementService;
#Autowired
ImportDocumentProcessDAO importDocumentProcessDAO;
#Autowired
ImportDocumentTraitementDAO importDocumentTraitementDAO;
// ---- Constants to name camel headers and bodies
private static final String HEADER_ENTREPRISE = "entreprise";
private static final String HEADER_UTILISATEUR = "utilisateur";
private static final String HEADER_IMPORTDOCPROCESS = "importDocumentProcess";
public void getImportDocumentProcess(#Header(HEADER_ENTREPRISE) Entreprise entreprise, Exchange exchange) {
LOGGER.info("Entering TestExampleProcessor method : getImportDocumentProcess");
Utilisateur utilisateur = SessionUtils.getUtilisateur();
ImportDocumentProcess importDocumentProcess = importDocumentProcessDAO.getImportDocumentProcessByEntreprise(
entreprise);
exchange.getIn().setHeader(HEADER_UTILISATEUR, utilisateur);
exchange.getIn().setHeader(HEADER_IMPORTDOCPROCESS, importDocumentProcess);
}
public void createImportDocumentTraitement(#Header(HEADER_ENTREPRISE) Entreprise entreprise,
#Header(HEADER_UTILISATEUR) Utilisateur utilisateur,
#Header(HEADER_IMPORTDOCPROCESS) ImportDocumentProcess importDocumentProcess, Exchange exchange) {
LOGGER.info("Entering TestExampleProcessor method : createImportDocumentTraitement");
long nbImportTraitementBefore = this.importDocumentTraitementDAO.countNumberOfImportDocumentTraitement();
ImportDocumentTraitement importDocumentTraitement = this.importDocumentTraitementService.createImportDocumentTraitement(
entreprise, utilisateur, importDocumentProcess, "md5_fichier_example_test", "fichier_example_test.xml");
long nbImportTraitementAfter = this.importDocumentTraitementDAO.countNumberOfImportDocumentTraitement();
exchange.getIn().setHeader("nbImportTraitementBefore", Long.valueOf(nbImportTraitementBefore));
exchange.getIn().setHeader("nbImportTraitementAfter", Long.valueOf(nbImportTraitementAfter));
exchange.getIn().setHeader("importDocumentTraitement", importDocumentTraitement);
}
}
I have read a few things about AdviceWith and WeaveById and I would like to put test the state of the exchange between two pieces of route.
Here is my attempt for a processor test :
#ContextConfiguration(locations = { "classpath:/camel-context.xml" })
public class TestExampleProcessorTest extends CamelTestSupport {
#Override
protected RouteBuilder createRouteBuilder() {
return new TestExampleRoute();
}
#Override
public boolean isUseAdviceWith() {
return true;
}
#Before
public void mockEndPoints() throws Exception {
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
weaveById("getImportDocumentProcess").replace().multicast().to("mock:catchTestEndpoint");
}
});
}
#Test
public void testAdvised() throws Exception {
MockEndpoint mockEndpoint = getMockEndpoint("mock:catchTestEndpoint");
context.start();
mockEndpoint.expectedMessageCount(1);
mockEndpoint.assertIsSatisfied();
context.stop();
}
}
One last thing : I am using Camel 2.18.0.
How can I test the state of the exchange between each piece of route ?
What am I missing ?
EDIT : Just edited the code of the the test class (Which compiles and works) BUT I get the following assertion error :
java.lang.AssertionError: mock://catchTestEndpoint Received message count. Expected: <1> but was: <0>
This adds one more question : why is the message not caught correctly ?
Thanks for your help.
Do you send any message to you testroute? I can't see that in the code. For example
template.sendBody("direct:testExampleEndpoint", "Hello World");
I have the following RouteBuilder:
MyRouteBuilder.java
#Component
public class MyRouteBuilder extends SpringRouteBuilder {
#Autowired
private MQConnectionProperties mqConnectionProperties;
#Override
public void configure() {
setupExceptionHandler();
setupTransformerReceiveChannel();
setupOrchestrationChannel();
}
private void setupExceptionHandler() {
onException(Exception.class).handled(true).to("direct:error");
}
private void setupTransformerReceiveChannel() {
from(mqConnectionProperties.getTransformerReceiveQueue())
.routeId(TransformerConstants.TRANSFORM_CONSUME_ROUTE)
.log("Processing transform request.")
.setHeader(TransformerConstants.TRANSFORM_HEADER_STATUS, simple("SUCCESS"))
.to("direct:cosTransform")
.end();
}
private void setupOrchestrationChannel() {
from("direct:cosTransform")
.routeId(TransformerConstants.TRANSFORM_XSLT_ROUTE)
.process(new XSLTConfigurationProcessor())
.log("Executing an xsl transform for Market=${header.market} and LOB=${header.lineOfBusiness}")
.choice().id("transformBranch")
.when(header("market").isEqualTo("NI"))
.process(new TransformerNIProcessor()).id("NITransform")
.endChoice()
.otherwise()
.recipientList(simple("xslt:./xsl/${header.market}/${header.lineOfBusiness}.xsl?saxon=true&contentCache=false")).id("BITransform")
.end();
}
}
Note: Exception handler is defined as a separate routeBuilder class. I have the following unit test for MyRouteBuilder.
MyRouteBuilderTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = MyRouteTestConfiguration.class)
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class TransformerRouteBuilderUnitTest extends CamelTestSupport{
#Autowired
MQConnectionProperties mqConnectionProperties;
#Autowired
MyRouteBuilder myRouteBuilder;
MockEndpoint mockOutput;
MockEndpoint mockError;
MockEndpoint mockProcessor;
#Override
public boolean isUseAdviceWith() {
return true;
}
#Override
protected RoutesBuilder createRouteBuilder() {
return myRouteBuilder;
}
#Before
public void setup() throws Exception {
super.setUp();
}
#Override
public String isMockEndpoints() {
return "direct:cosTransform";
}
#Test
public void test_transformerReceiveChannel_happyPath_transformStatusHeaderSet() throws Exception {
startCamelContext();
mockOutput = getMockEndpoint("mock:direct:cosTransform", false);
mockOutput.expectedHeaderReceived("TransformStatus", "fail");
template.requestBody(mqConnectionProperties.getTransformerReceiveQueue(), new DefaultExchange(context));
MockEndpoint.assertIsSatisfied(context);
stopCamelContext();
}
stack trace
org.apache.camel.CamelExecutionException: Exception occurred during execution on the exchange: Exchange[ID-LIBP03P-QK70A9V-57085-1490361963523-1-2]
at org.apache.camel.util.ObjectHelper.wrapCamelExecutionException(ObjectHelper.java:1706)
at org.apache.camel.util.ExchangeHelper.extractResultBody(ExchangeHelper.java:660)
at org.apache.camel.impl.DefaultProducerTemplate.extractResultBody(DefaultProducerTemplate.java:471)
at org.apache.camel.impl.DefaultProducerTemplate.sendBody(DefaultProducerTemplate.java:133)
at org.apache.camel.impl.DefaultProducerTemplate.sendBody(DefaultProducerTemplate.java:149)
at org.apache.camel.impl.DefaultProducerTemplate.requestBody(DefaultProducerTemplate.java:301)
at com.transformer.routing.MyRouteBuilderUnitTest.test_transformerReceiveChannel_happyPath_transformStatusHeaderSet(TransformerRouteBuilderUnitTest.java:89)
.....
Caused by: org.apache.camel.component.direct.DirectConsumerNotAvailableException: No consumers available on endpoint: Endpoint[direct://error]. Exchange[ID-LIBP03P-QK70A9V-57085-1490361963523-1-4]
....
I've read a few solutions to this and most answers revolve around failing to start the camel context, or having a second context that is being used rather than the context with the modified route etc., but I don't think that's the case here; debugging shows only 1 context which definately gets started, which only contains a route definition for MyRouteBuilder(as opposed to the route + the exception handling route).
I've probably completly missed something but I can't manage to test my route as I want to.
I've got the following bean :
#Component("fileProcessor")
public class FileProcessor {
public boolean valid(#Header("customObject) CustomObject customObject,Exchange exchange) throws IOException{
return false;
}
I've a route calling my bean like so :
from("direct:validationFile").routeId("validationFile").validate().method("fileProcessor","valid")
// Other stuff
.end();
Here is my unit test, based on a example I found:
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class})
#ContextConfiguration(locations = { "classpath:/tu-dao-beans.xml" })
public class FileProcessorTest extends CamelTestSupport {
#EndpointInject(uri = "mock:result")
protected MockEndpoint resultEndpoint;
#Produce(uri = "direct:start")
protected ProducerTemplate template;
#Override
public boolean isDumpRouteCoverage() {
return true;
}
#Test
public void testSendMatchingMessage() throws Exception {
String expectedBody = "<matched/>";
resultEndpoint.expectedBodiesReceived(expectedBody);
template.sendBodyAndHeader(expectedBody, "foo", "bar");
resultEndpoint.assertIsSatisfied();
}
#Test
public void testSendNotMatchingMessage() throws Exception {
resultEndpoint.expectedMessageCount(0);
template.sendBodyAndHeader("<notMatched/>", "foo", "notMatchedHeaderValue");
resultEndpoint.assertIsSatisfied();
}
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
public void configure() {
// from("direct:start").filter(header("foo").isEqualTo("bar")).to("mock:result");
from("direct:start").routeId("validationFile").validate().method("fileProcessor","valid").to("mock:result");
}
};
}
}
The test fails because fileProcessor is not found, yet I'm pretty sure my spring contextis properly loaded, I'm using the same beans.xmlfile for my dbunit tests and my DAO components are found just fine... What am I missing ?
EDIT:
Thanks to Jérémis B's answer I fixed my problem easily. In case someone stumble as I did here is the code I added:
#Autowired
private FileProcessor fileProcessor;
#Override
protected JndiRegistry createRegistry() throws Exception {
JndiRegistry registry = super.createRegistry();
registry.bind("fileProcessor", fileProcessor);
return registry;
}
You can see the official documentation for an "How to" test with Spring.
In your example, you create a Spring Context, but use the CamelTestSupport : This class create a CamelContext which is not aware of the Spring Context. The bean "fileProcessor" is not seen by this context.
There is a lot of ways to do this kind of test. The easiest, with the code you already have, is maybe to:
Inject the fileProcessor in your test class, with #Autowire
Override createRegistry and add the fileProcessor to the registry
You can too override CamelSpringTestSupport and implement createApplicationContext. Another way is to keep the route definition in a Spring Bean (through xml, or a RouteBuilder), and inject in your test MockEndpoints or ProducerTemplate.
Hi I have application using apache camel and input queue that is a start point of the processing. I'm trying to find a nice way to mock somehow this input queue so :
I reuse the production routing file, I don't want to copy and paste the contents and make just one change for the routing of the queue
I can send the message to this 'mocked' queue and processing is done as in production
This is probably about changing 'queue:' into 'direct:' routing, but I couldn't find any other way than specifying another xml.
You can use Camels AdviceWith method to intercept messages during testing:
public class MySuperTest extends CamelTestSupport {
public void testAdvised() throws Exception {
// advice the first route using the inlined route builder
context.getRouteDefinitions().get(0).adviceWith(context, new RouteBuilder() {
#Override
public void configure() throws Exception {
// intercept sending to mock:foo and do something else
interceptSendToEndpoint("mock:foo")
.skipSendToOriginalEndpoint()
.to("log:foo")
.to("mock:advised");
}
});
getMockEndpoint("mock:foo").expectedMessageCount(0);
getMockEndpoint("mock:advised").expectedMessageCount(1);
getMockEndpoint("mock:result").expectedMessageCount(1);
template.sendBody("direct:start", "Hello World");
assertMockEndpointsSatisfied();
}
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
#Override
public void configure() {
//TODO build your route here
from("direct:start").process(...).to("mock:result");
}
};
}
}
You can advise a route to replace your from-component, e.g. replace the amq endpoint with a direct endpoint. You can then use a producer template to trigger the route in your test.
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "/META-INF/spring/your-context.xml" })
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
#MockEndpoints("none")
#UseAdviceWith
public class ReplaceFromTest {
#Autowired
protected CamelContext context;
#Produce(context = "your-camel-context-id")
protected ProducerTemplate template;
#Before
public void setUp() throws Exception {
AdviceWithRouteBuilder mockAmq = new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceFromWith("direct:amq-mock");
}
};
((ModelCamelContext) context).getRouteDefinition("route_to_advise").adviceWith((ModelCamelContext) context, mockAmq);
context.start();
}
#After
public void tearDown() throws Exception {
context.stop();
}
#DirtiesContext
#Test
public void sendMessageTest() {
Map<String, Object> myHeaders = new HashMap<>();
String myBody = "Some content";
template.sendBodyAndHeaders("direct://amq-mock", myBody, myHeaders);
// Verify the results
}
}
HTH.
I have created class like this
import org.apache.camel.CamelContext;
public class JmsToSedaComponent {
private CamelContext camelContext;
public JmsToSedaComponent(CamelContext camelContext) {
this.camelContext = camelContext;
}
public void init() {
camelContext.removeComponent("jms");
camelContext.addComponent("jms", camelContext.getComponent("seda"));
}
}
and then in the spring xml file:
<bean class="com.lmig.ci.baods.dial.integration.JmsToSedaComponent" init-method="init">
<constructor-arg ref="camelContext"/>
</bean>
This replaces all JMS components to SEDA.
I have a Camel app that is integrated with spring and i would like to write tests for it. Here's my app:
camel-config.xml
<camelContext xmlns="http://camel.apache.org/schema/spring">
<routeBuilder ref="converter" />
</camelContext>
<bean id="converter" class="Converter"/>
class to be tested:
#Component
public class Converter extends SpringRouteBuilder {
#Override
public void configure() throws Exception {
final XmlJsonDataFormat xmlJsonFormat = new XmlJsonDataFormat();
xmlJsonFormat.setTypeHints(String.valueOf("YES"));
from("ftp://Mike#localhost?" +
"noop=true&binary=true&consumer.delay=5s&include=.*xml")
.idempotentConsumer(header("CamelFileName"), FileIdempotentRepository.fileIdempotentRepository(new File("data", "repo.dat")))
.marshal(xmlJsonFormat).to("file://data").process(
new Processor() {
//System.out.println();
}
});
}
}
And here's my testing class:
public class RouteTest extends CamelTestSupport {
#Override
protected CamelContext createCamelContext() throws Exception {
CamelContext context = super.createCamelContext();
context.addComponent("ftp", context.getComponent("seda"));
return context;
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("ftp://Mike#localhost").to("mock:quote");
}
};
}
#Test
public void testSameMessageArrived() throws Exception {
MockEndpoint quote = getMockEndpoint("mock:quote");
FileReader fl = new FileReader("D:\\test\\asdasd.txt");
quote.expectedBodiesReceived(fl);
template.sendBody("ftp://Mike#localhost", fl);
quote.assertIsSatisfied();
}
}
This test passes, but I'm not sure that it's the right way to test this particular program.
Could you please tell me if I'm doing it right, or I should test it other way?
A better way would be to not rewrite the route. Use your actual route instead.
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new Converter();
}
And then use camel-mock, this let you intercept existing endpoint like this:
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
// mock all endpoints
mockEndpoints();
}
});
getMockEndpoint("mock:direct:start").expectedBodiesReceived("Hello World");
Or with patterns:
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
// mock only log endpoints
mockEndpoints("log*");
}
});
// now we can refer to log:foo as a mock and set our expectations
getMockEndpoint("mock:log:foo").expectedBodiesReceived("Bye World");
If you want more info on testing with Camel, I seriously advice you to read the "Camel in Action" book.
EDIT: here is a response of Claus Ibsen to a similar question (stack).
You can do integration testing with a dedicated or embedded FTP server or you can use mocks for unit testing, depending on what you want to test. You could also do both.