I have a camel route that sends to loadbalancer and processes the response. Is it possible to mock that response somehow in unit test? I tried to use velocity but it doesn't seem to work in unit tests.
Apache already takes care of such testing requirements. There is adviceWith construct which will solve this problem.
Quoting the example directly with few modifications from the link mentioned above:
#Test
public void testAdvised() throws Exception {
context.addRoutes(new RouteBuilder() {
#Override public void configure() throws Exception {
from("direct:start").id("my-route").to("mock:foo");
}
});
context.getRouteDefinition("my-route").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:result");
}
});
getMockEndpoint("mock:foo").expectedMessageCount(0);
getMockEndpoint("mock:result").expectedMessageCount(1);
template.sendBody("direct:start", "Hello World");
assertMockEndpointsSatisfied();
}
Now here a route is defined as:
from("direct:start").id("my-route").to("mock:foo");
And let's say we want to mock the to part here.
This is precisely doing it for me:
context.getRouteDefinition("my-route").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:result");
}
});
We get the reference of the route definition we want to modify from CamelContext and using the adviceWith method we can define what all action needs to be done. As here, I advised not to send to actual destination i.e. mock:foo, rather send to two other routes log:foo and mock:result.
Hope it answers your query.
Related
I am trying to build an central error handling using Camel.
I want that all my routes exposing REST endpoints would have transparent error handling translating Exceptions into HTTP error codes in one piece of code.
Currently I achieve this with an Abstract RouteBuilder defining an onException route in its Contructor. When I inherit it in a concrete RouteBuilder it works fine.
However this is not transparent since I have to know that I have to inherit from the abstract class to have it working.
Is there any mechanism in Camel similar to Jersey ExceptionMapper?
What I need is a place in the Camel framework where I can configure the error handling without the need to ensure that any team member has to remember to inherit the abstract error handling class.
what I currently have
public abstract class ErrorHandlerRoute extends RouteBuilder {
public ErrorHandlerRoute () {
super();
onException().handled(true)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Exception cause = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
//handle exceptions here
exchange.getOut().setHeader(Exchange.HTTP_RESPONSE_CODE, HttpStatus.SC_INTERNAL_SERVER_ERROR);
exchange.getOut().setFault(false);
});
}
}
public class MyBusinessRoute extends ErrorHandlerRoute {
#Override
public void configure() throws Exception {
//OK, I know that my errors will be handled
rest()
.get("/{param}")
to("http4://backend");
}
}
New to Camel, so maybe I'm misunderstanding how processors and beans should interact. We have some logging to a database that we want to do throughout a camel route. The idea was to do this in a processor. However, we'd also like to do this logging from w/in the beans. Is that possible? I know I could pass it back as a return field from the bean...but it is already passing back a return.
A related question is how to pass that status, thinking it would be an exchange property or header.
Basically I want something along the lines of
processor
class EventStatusProcessor implements Processor {
#Override
void process(Exchange exchange) throws Exception {
// do some stuff, thinking this will be a header
}
}
route
from("direct:route1")
.bean(doSomething, 'doSomething')
.process(new EventStatusProcessor())
bean
#Component
#Slf4j
class DoSomething{
String doSomething()
//doing stuff
new EventStatusProcessor().process()
You can pass Exchange to method invoked with bean component too and set there headers/properties/body/whatever depending on your needs.
class DoSomething {
#SuppressWarnings("unused") //called via Camel bean invocation
public void doSomething(Exchange exchange){
exchange.setProperty("propertyFromDoSomething", "Hello, I am property");
exchange.getIn().setHeader("headerFromDoSomething", "Hi, I am header");
exchange.getIn().setBody("It's me, body!");
}
}
class EventStatusProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println(exchange.getIn().getHeader("headerFromDoSomething", String.class));
System.out.println(exchange.getProperty("propertyFromDoSomething", String.class));
System.out.println(exchange.getIn().getBody(String.class));
}
}
If you really need to call processor inside bean, as you are writing in title, extract processor to direct route and then invoke it with ProducerTemplate.
RouteBuilder
from("direct:log")
.process(new EventStatusProcessor());
DoSomething class
public class DoSomething {
#SuppressWarnings("unused") //called via Camel bean invocation
public void doSomething(Exchange exchange){
exchange.getContext().createProducerTemplate().sendBody("direct:log", "I am body and I will be passed to EventStatusProcessor");
}
}
Camel has to call REST service for some integration, However, the REST service has one authentication api (POST api) which needs to be called first to get a token and then other subsequent api calls has to be invoked with the token embedded in header of HTTP requests.
Does Spring Restemplate or apache camel has some api to support the same?
Followed #gusto2 approach, Its pretty much working fine.
SO, I created two routes --> First one is a timer based like below, this generates the token, periodically refreshes it(since the route is timer based) and stores the token in a local variable for being reused by some other route.
#Component
public class RestTokenProducerRoute extends RouteBuilder {
private String refreshedToken;
#Override
public void configure() throws Exception {
restConfiguration().producerComponent("http4");
from("timer://test?period=1200000") //called every 20 mins
.process(
exchange -> exchange.getIn().setBody(
new UserKeyRequest("apiuser", "password")))
.marshal(userKeyRequestJacksonFormat) //convert it to JSON
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("http4://localhost:8085/Service/Token")
.unmarshal(userKeyResponseJacksonFormat)
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
UserKeyResponse response= exchange.getIn().getBody(
UserKeyResponse.class); //get the response object
System.out.println(response + "========>>>>>>" +
response.getResult());
setRefreshedToken(response.getResult()); //store the token in some object
}
}).log("${body}");
}
public String getRefreshedToken() {
return refreshedToken;
}
public void setRefreshedToken(String refreshedToken) {
this.refreshedToken = refreshedToken;
}
}
And the second route can call subsequent apis which will use the token generated by the first route, it would be something like this. Have to add error handling scenarios, where token might not be valid or expired. But I guess that would be separate concern to solve.
#Component
public class RestTokenUserOnboardRoute extends RouteBuilder {
private JacksonDataFormat OtherDomainUserRequestJacksonFormat = new JacksonDataFormat(
OtherDomainUserRequest.class);
private JacksonDataFormat OtherDomainUserResponseJacksonFormat = new JacksonDataFormat(
OtherDomainUserResponse.class);
#Override
public void configure() throws Exception {
restConfiguration().producerComponent("http4");
//This route is subscribed to a Salesforce topic, which gets invoked when there is any new messages in the topic.
from("salesforce:CamelTestTopic?sObjectName=MyUser__c&sObjectClass="+MyUser__c.class.getName()))
.convertBodyTo(OtherDomainUserRequest.class)
.marshal(OtherDomainUserRequestJacksonFormat).log("${body}")
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.log("The token being passed is ==> ${bean:tokenObj?method=getRefreshedToken}")
.setHeader("Authorization", simple("${bean:tokenObj?method=getRefreshedToken}"))
.to("http4://localhost:8085/Service/DomainUser")
.unmarshal(OtherDomainUserResponseJacksonFormat)
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
OtherDomainUserResponse response = exchange.getIn().getBody(
OtherDomainUserResponse.class);
System.out.println(response + "==================>>>>>> " + response.getStatusCode());
}
}).log("${body}");
}
}
So, here the token is getting consumed from the tokenObj bean (instance of RestTokenProducerRoute which has method getRefreshedToken() defined. It returns the stored token.
Needless to say, you have set the bean in camelcontext registry as follows along with other settings (like component, route etc). In my case it was as follows.
#Autowired
public RestTokenUserOnboardRoute userOnboardRoute;
#Autowired
public RestTokenProducerRoute serviceTokenProducerRoute;
#Autowired
private RestTokenProducerRoute tokenObj;
#Override
protected CamelContext createCamelContext() throws Exception {
SimpleRegistry registry = new SimpleRegistry();
registry.put("tokenObj", tokenObj); //the tokenObj bean,which can be used anywhere in the camelcontext
SpringCamelContext camelContext = new SpringCamelContext();
camelContext.setRegistry(registry); //add the registry
camelContext.setApplicationContext(getApplicationContext());
camelContext.addComponent("salesforce", salesforceComponent());
camelContext.getTypeConverterRegistry().addTypeConverter(DomainUserRequest.class, MyUser__c.class, new MyTypeConverter());
camelContext.addRoutes(route()); //Some other route
camelContext.addRoutes(serviceTokenProducerRoute); //Token producer Route
camelContext.addRoutes(userOnboardRoute); //Subsequent API call route
camelContext.start();
return camelContext;
}
This solves my problem of setting token dynamically where token is getting produced as a result of execution of some other route.
I am trying to create testcases for my camel route using http://camel.apache.org/mock.html . I need to verify the the processors in the route. But the simple test is not working for me.
public class CamelRouteTest extends CamelTestSupport {
#Override
public String isMockEndpointsAndSkip() {
// override this method and return the pattern for which endpoints to mock,
// and skip sending to the original endpoint.
return "mock:result";
}
#Test
public void verifyMessageCount() throws Exception {
template.sendBody("Test");
getMockEndpoint("mock:result").expectedMessageCount(1);
assertMockEndpointsSatisfied();
}
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start").to("mock:result");
}
};
}
}
Stacktrace:
java.lang.IllegalArgumentException: defaultEndpoint must be specified
at org.apache.camel.util.ObjectHelper.notNull(ObjectHelper.java:308)
at org.apache.camel.impl.DefaultProducerTemplate.getMandatoryDefaultEndpoint(DefaultProducerTemplate.java:506)
at org.apache.camel.impl.DefaultProducerTemplate.sendBody(DefaultProducerTemplate.java:370)
The template.sendBody("Test") try to send Test to the default endpoint. As in your code this is not configured it fails.
You could:
specify which endpoint to use
template.sendBody("direct:start", "Test");
get an endpoint from the context and set it as the default endpoint
Endpoint endpoint = context.getEndpoint("direct:start");
template.setDefaultEndpoint(endpoint);
template.sendBody("Test");
Trying to unit test a Retrofit 2.0 response using MockWebServer. I have the webserver setup, but the problem occurs when i am trying to pass a mock json file as a response. To illustrate, my folder structure is:
src->test->java->package_name->class_name.java
src->test->resources->list_success.json
My unit test:
#Before
public void setUp() throws Exception{
MockitoAnnotations.initMocks(this);
server = new MockWebServer();
serviceHelper = new ServiceHelper();
}
#Test
public void testEventBusIdPostedOnSuccessfulServiceCall() throws Exception {
server.start();
server.enqueue(new MockResponse().setResponseCode(200).setBody(getStringFromFile(RuntimeEnvironment.application, "list_success.json")));
MainActivity.URL = server.url("/").toString();
serviceHelper.getIndividualData("Store","7");
verify(eventBus).post(any());
}
#After
public void tearDown() throws Exception{
server.shutdown();
}
Everything works fine. The retrofit call which is async is triggered with the mock JSON. However, the callback never gets triggered. I tried putting in a countdown latch within the actual service implementation, but nothing happens either.
public void getIndividualData(String item, String number) {
Call<DataList> dataList = RestClient.get().getList(item, number);
dataList.enqueue(new Callback<DataList>() {
#Override
public void onResponse(Response<DataList> response, Retrofit retrofit) {
/*data persistence should take place before sending out the eventbus message.
Passing the response object directly for sample purpose. */
EventBus.getDefault().post(new IndividualItemEvent(response.body().Values));
// latch.countDown(); ---->might be needed for retrofit async unit testing
}
#Override
public void onFailure(Throwable t) {
Log.d("%%%%%", "retrofit failure");
// latch.countDown(); ---->might be needed for retrofit async unit testing
}
});
// try {
// latch.await();
// } catch (InterruptedException e) { ---->might be needed for retrofit async unit testing
// e.printStackTrace();
// }
Am i missing something here?
Thanks.