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");
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'm trying to unit test the code of my Presenter. As you can see below in the code I'm making a Retrofit request and if the response is successful I call a method from the View.
Code of my Presenter I want to test :
#Override
public void onLoadChatrooms(String accountId, String pageNum) {
getChatroomsService.getChatrooms(apiToken, createRequestBodyForGetChatroomsRequest(accountId, pageNum))
.enqueue(new Callback<GetChatroomsServiceResponse>() {
#Override
public void onResponse(Call<GetChatroomsServiceResponse> call, Response<GetChatroomsServiceResponse> response) {
if (response.isSuccessful()) {
view.showData(Arrays.asList(response.body().getChatRoomsArray()));
}
}
#Override
public void onFailure(Call<GetChatroomsServiceResponse> call, Throwable t) {
}
});
}
And here is the test I wrote :
#Mock
private ChatMVP.View view;
#Mock
private GetChatroomsService getChatroomsService;
#Mock
private RequestBody requestBody;
#Mock
private Call<GetChatroomsServiceResponse> call;
#Captor
private ArgumentCaptor<Callback<GetChatroomsServiceResponse>> callback;
#Mock
private List<GetChatroomsResponseNestedItem> chatroomsResponseNestedItems;
private String accountId = "14";
private String apiToken = "someToken";
private ChatPresenter chatPresenter;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
chatPresenter = new ChatPresenter(view, getChatroomsService, apiToken);
}
#Test
public void onLoadChatrooms() throws Exception {
when(getChatroomsService.getChatrooms(apiToken, requestBody))
.thenReturn(call);
chatPresenter.onLoadChatrooms(accountId, "0");
verify(call).enqueue(callback.capture());
callback.getValue().onResponse(call, getResponse());
verify(view).showData(chatroomsResponseNestedItems);
}
The problem is that I'm getting a NPE for line :
chatPresenter.onLoadChatrooms(accountId, "0");
The exact error message is :
java.lang.NullPointerException
at my.package.main.fragments.chat.ChatPresenter.onLoadChatrooms(ChatPresenter.java:40)
at my.package.main.fragments.chat.ChatPresenterTest.onLoadChatrooms(ChatPresenterTest.java:70)
where line 40 for ChatPresenter is : .enqueue(new Callback<GetChatroomsServiceResponse>() {
Anyone can help with that ? I tried checking if Presenter is null and that's not the problem.
EDIT :
ChatPresenter's constructor :
class ChatPresenter implements ChatMVP.Presenter {
private ChatMVP.View view;
private GetChatroomsService getChatroomsService;
private String apiToken;
#Inject
ChatPresenter(ChatMVP.View view, GetChatroomsService getChatroomsService, #Named("Api-Token") String apiToken) {
this.view = view;
this.getChatroomsService = getChatroomsService;
this.apiToken = apiToken;
}
and GetChatroomsService :
interface GetChatroomsService {
#POST("getchatrooms")
Call<GetChatroomsServiceResponse> getChatrooms(#Query("api_token") String apiToken, #Body RequestBody requestBody);
}
The problem here is that the mocked method getChatrooms() in getChatroomsService returns a null. The most likely reason for this is that the parameters given in your production code do not match the parameters in your mock configuration.
I for myself use the any*() matcher when configuring the mocks and verify the parameters passed in by the production code explicitly which saves me from non descriptive NPEs like this.
#Test
public void onLoadChatrooms() throws Exception {
when(getChatroomsService.getChatrooms(anyString(), any(RequestBody.class)))
.thenReturn(call);
chatPresenter.onLoadChatrooms(accountId, "0");
verify(call).enqueue(callback.capture());
callback.getValue().onResponse(call, getResponse());
verify(getChatroomsService).getChatrooms(apiToken,requestBody);
verify(view).showData(chatroomsResponseNestedItems);
}
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 have a situation I cannot explain.
I am trying to create a RMI server that will call Spring services, but I cannot bind the beans to the rmi registry because they all are nulls.
The code is like this:
public class RMIServer {
private static final Logger LOG = LoggerFactory.getLogger(RMIServer.class);
public static void main(final String[] args) throws RemoteException, AlreadyBoundException {
final Registry registry = LocateRegistry.createRegistry(RmiConstants.RMI_PORT);
final ApplicationContext ctx = new AnnotationConfigApplicationContext(RmiConfiguration.class);
for (final String key : ctx.getBeansOfType(BaseRmi.class).keySet()) {
LOG.info("Registering {}...", key);
registry.bind(key, (BaseRmi) ctx.getBean(key));
}
LOG.info("RMI server was started...");
}
}
The Spring configuration class is:
#Configuration
#ImportResource({ "classpath*:app-context.xml" })
public class RmiConfiguration {
#Bean
AccountRmi accountRmi() {
try {
return new AccountRmiImpl();
} catch (final RemoteException e) {
return null;
}
}
}
The bean that I want to instantiate is this:
public class AccountRmiImpl extends BaseRmi implements AccountRmi {
private static final long serialVersionUID = 5798106327227442204L;
private final static Logger LOG = LoggerFactory.getLogger(AccountRmiImpl.class.getName());
#Autowired
private AccountService accountService;
public AccountRmiImpl() throws RemoteException {
super();
}
#Override
public List<PersonType> getPersonTypes() throws AppException {
return accountService.getPersonTypes();
}
}
The BaseRmi is:
public abstract class BaseRmi extends UnicastRemoteObject {
protected BaseRmi() throws RemoteException {
super();
}
private static final long serialVersionUID = 9115163829282749718L;
}
The interface for this bean is this:
public interface AccountRmi extends AccountFacade, java.rmi.Remote {
}
where the AccountFacade contains the business logic.
What I saw is that if I remove the java.rmi.Remote interface on the AccountRmi interfaces declaration the bean gets instantiated but I need that interface for the remote lookup. No errors are getting displayed in the logs. Does spring has a limitation for multiple interfaces on a bean declaration or it's just because of the java.rmi.Remote interfaces ?
I can provide further details if requested.
Many thanks,
Daniel
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.