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.
Related
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.
I try to make a junit test for apache camel route.
Something like this :
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(
loader = CamelSpringDelegatingTestContextLoader.class
)
public class MyExportRouteBuilderIT extends CamelTestSupport {
#Test
public void test() {
// trigger and check the files made by route builder processor
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new MyExportRouteBuilder();
}
}
The builder class is defined like this
from("quartz2://exportJob?cron=" + cronTrigger)
.setHeader(FILE_NAME, expression(FILE_NAME_FORMAT))
.process(myExportRouteProcessor)
.marshal(new BindyCsvDataFormat(MyExportData.class))
.to("file:///destination);
The 'myExportRouteProcessor' class just gets some data from the JPA repository and puts the results to the route.
What I want is to trigger this route in the test class to check if the whole process was properly finished.
Currently, processor is not fired. What should I do more ?
You can replace quartz2 component in your test with direct using AdviceWithRouteBuilder#replaceFromWith.
#Test
public void test() throws Exception{
//mock input route (replace quartz with direct)
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceFromWith("direct:triggerQuartz");
}
});
//trigger endpoint
sendBody("direct:triggerQuartz", null);
//do some assertions
}
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.
i m running a test with CamelTestSupport,
public class TestIntegrationBeanCtrlContrat extends CamelTestSupport {
#EndpointInject(uri = "mock:result")
protected MockEndpoint resultEndpoint;
#Produce(uri = "direct:start")
protected ProducerTemplate template;
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
#Override
public void configure() {
this.from("direct:start")
.bean(MyClassA.class, "methodOfMyClassA").to("mock:result");
}
};
}
#Test
public void test_ControleBean_Integration() {
this.template.sendBody(....);
}
I m trying to put the body of another bean to the producer template , for exemple :
template.sendBody( bean(MyClassB.class, "methodOfMyClassB") )
Is it possible to do that ?
In general How can i do to setup the input in the produceTemplace.
I’m not sure I understand your needs but if you want to inject the result of some bean in the route process you should use Camel Mock to inject the bean process (MyClassB.methodOfMyClassB() in your example):
#EndpointInject(uri = "mock:result")
protected MockEndpoint resultEndpoint;
#Produce(uri = "direct:start")
protected ProducerTemplate template;
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start").bean("BeanA", "methodA").to("mock:beanB").to("mock:result");
}
};
}
#Test
public void test() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:beanB");
mock.whenAnyExchangeReceived(new Processor() {
public void process(Exchange exchange) throws Exception {
// call the method of your class here
exchange.getIn().setBody(MyClassB.methodOfMyClassB());
}
});
template.sendBody("Your message body...");
// check some results
mock.assertIsSatisfied();
}
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.