Camel: How to mock a route with two endpoints - java

I'm new to Camel and I need to understand how to unit test my route that has two endpoints. The first endpoints gets a user ID and uses that for the second endpoint.
public RouteBuilder routeBuilder() {
return new RouteBuilder() {
#Override
public void configure() throws HttpOperationFailedException {
this.from(MyServiceConstant.ROUTE)
.setHeader(...)
.setHeader(...)
.to(MyConstants.THE_FIRST_ROUTE)
.setHeader(...)
.setHeader(...)
.process(...)
.setProperty(...)
.to(MyConstants.THE_SECOND_ROUTE)
}
};
}
So I have to mock both the MyConstants.THE_FIRST_ROUTE and MyConstants.THE_SECOND_ROUTE in my Test class. I did that but am not sure how to write the test. All I'm doing is hitting the second endpoint but don't know how to trigger the first.
#Produce(uri = MyServiceConstant.ROUTE)
private MyService myService;
#EndpointInject(uri = "mock:" + MyConstants.THE_FIRST_ROUTE)
private MockEndpoint mockFirstService;
#EndpointInject(uri = ""mock:" + MyConstants.THE_SECOND_ROUTE)
private MockEndpoint mockSecondService;
#Test
#DirtiesContext
public void getDetails()throws Exception {
// **The missing part**: Is this the right way to call my first service?
this.mockFirstService.setUserId("123456");
// this returns a JSON that I'll compare the service response to
this.mockSecondService.returnReplyBody(...PATH to JSON file);
UserDetail userDetailsInfo = this.myService.getUserDetails(...args)
// all of my assertions
assertEquals("First name", userDetailsInfo.getFirstName());
MockEndpoint.assertIsSatisfied();
}

I got some time today to quickly hack around some demo code, with Camel Spring boot archetype. Here we go. My route produces messages from a timer component. Explicit delivery to an endpoint is not used.
//Route Definition - myBean::saySomething() always returns String "Hello World"
#Component
public class MySpringBootRouter extends RouteBuilder {
#Override
public void configure() {
from("timer:hello?period={{timer.period}}").routeId("hello_route")
.transform().method("myBean", "saySomething")
.to("log:foo")
.setHeader("test_header",constant("test"))
.to("log:bar");
}
}
#RunWith(CamelSpringBootRunner.class)
#SpringBootTest
public class MySpringBootRouterTest {
#Autowired
SpringCamelContext defaultContext;
#EndpointInject("mock:foo")
private MockEndpoint mockFoo;
#EndpointInject("mock:bar")
private MockEndpoint mockBar;
#Test
#DirtiesContext
public void getDetails() throws Exception {
assertNotNull(defaultContext);
mockBar.expectedHeaderReceived("test_header", "test");
mockBar.expectedMinimumMessageCount(5);
MockEndpoint.setAssertPeriod(defaultContext, 5_000L);
MockEndpoint.assertIsSatisfied(mockFoo, mockBar);
mockFoo.getExchanges().stream().forEach( exchange -> assertEquals(exchange.getIn().getBody(),"Hello World"));
//This works too
//mockBar.assertIsSatisfied();
//mockFoo.assertIsSatisfied();
}
#Before
public void attachTestProbes() throws Exception {
//This is Camel 3.0 API with RouteReifier
RouteReifier.adviceWith(defaultContext.getRouteDefinition("hello_route"), defaultContext, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
//Hook into the current route, intercept log endpoints and reroute them to mock
interceptSendToEndpoint("log:foo").to("mock:foo");
interceptSendToEndpoint("log:bar").to("mock:bar");
}
});
}
}
Warning to visitors from future: The test case here demonstrates how to intercept log: endpoints with mock: and set expectations on them. The test case may not be testing anything worthwhile.

Here is a link to the Unit Test cases for the Mock component. It shows how to implement tests with mock: endpoints and CamelTestSupport. #Roman Vottner is completely right in his comment.
This test case may be of specific interest to you since it shows how to swap an smtp: endpoint with a mock: endpoint. Additionally, here is official documentation on how to mock existing endpoints (To use them like test probes).
Caveat: Please bear in mind that Camel 3.0 API is quite different from Camel 2.x API, in this region. Good luck!

Related

Apache Camel aggregation completion not working

I've configured a route to extract some data from exchanges and aggregate them; here is simple summary:
#Component
#RequiredArgsConstructor
public class FingerprintHistoryRouteBuilder extends RouteBuilder {
private final FingerprintHistoryService fingerprintHistoryService;
#Override
public void configure() throws Exception {
from("seda:httpFingerprint")
.aggregate( (AggregationStrategy) (oldExchange, newExchange) -> {
final FingerprintHistory newFingerprint = extract(newExchange);
if (oldExchange == null) {
List<FingerprintHistory> fingerprintHistories = new ArrayList<>();
fingerprintHistories.add(newFingerprint);
newExchange.getMessage().setBody(fingerprintHistories);
return newExchange;
}
final Message oldMessage = oldExchange.getMessage();
final List<FingerprintHistory> fingerprintHistories = (List<FingerprintHistory>) oldMessage.getBody(List.class);
fingerprintHistories.add(newFingerprint);
return oldExchange;
})
.constant(true)
.completionSize(aggregateCount)
.completionInterval(aggregateDuration.toMillis())
.to("direct:processFingerprint")
.end();
from("direct:processFingerprint")
.process(exchange -> {
List<FingerprintHistory> fingerprintHistories = exchange.getMessage().getBody(List.class);
fingerprintHistoryService.saveAll(fingerprintHistories);
});
strong text
}
}
The problem is aggregation completion never works for example this is a sample of my test:
#SpringBootTest
class FingerprintHistoryRouteBuilderTest {
#Autowired
ProducerTemplate producerTemplate;
#Autowired
FingerprintHistoryRouteBuilder fingerprintHistoryRouteBuilder;
#Autowired
CamelContext camelContext;
#MockBean
FingerprintHistoryService historyService;
#Test
void api_whenAggregate() {
UserSearchActivity activity = ActivityFactory.buildSampleSearchActivity("127.0.0.1", "salam", "finger");
Exchange exchange = buildExchange();
exchange.getMessage().setBody(activity);
ReflelctionTestUtils.setField(fingerprintHistoryRouteBuilder, "aggregateCount", 1);
ReflectionTestUtils.setFiled(fingerprintHistoryRouteBuilder, "aggregateDuration", Duration.ofNanos(1));
producerTemplate.send(FingerprintHistoryRouteBuilder.FINGERPRINT_HISTORY_ENDPOINT, exchange);
Mockito.verify(historyService).saveAll(Mockito.any());
}
Exchange buildExchange() {
DefaultExchange defaultExchange = new DefaultExchange(camelContext);
defaultExchange.setMessage(new DefaultMessage(camelContext));
return defaultExchange;
}
}
with the following result:
Wanted but not invoked: fingerprintHistoryService bean.saveAll(
);
I build this simplified example, and the test passes, so it looks like your usage of aggregate is probably correct.
Have you considered that your Mockito.verify() call is happening before the exchange finishes routing? You could test this by removing the verify call and adding a .log() statement to the FINGERPRINT_PROCESS_AGGREGATION route. If you see the log output during execution, you know the exchange is being routed as you expect. If this is the case, then your verify() call needs to be able to wait for the exchange to finish routing. I don't use mockito much, but it looks like you can do this:
Mockito.verify(historyService, timeout(10000)).saveAll(Mockito.any());

Unit test Spring Cloud Gateway RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)

I would like to properly unit test the Spring Cloud Gateway RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { method with JUnit5.
However, I am having a hard time figuring out what to test, what to assert, what to mock, how to improve coverage, etc...
If possible, I just want to unit test this, no need to start an entire SpringTest etc.
#Bean
#Override
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
return routeLocatorBuilder.routes()
.route("forward_to_service_one", r -> r.path("/serviceone/**").and().uri("http://the-first-service:8080"))
.route("forward_to_service_two", r -> r.path("/servicetwo/**").and().uri("http://the-second-service:8080"))
.route("forward_to_service_three", r -> r.alwaysTrue().and().order(Ordered.LOWEST_PRECEDENCE).uri("http://the-default-third-service:8080"))
.build();
}
While working with integration tests, hit the gateway service that is started on the endpoint, seeing the requests forwarded to respective services, I was wondering if there is a good practice to test this Spring Cloud Gateway feature.
Any example of fully covered test cases please?
Thank you
I could not understand your test scenarios (what do you want to test, if service is configured correctly for the path or?) But I would like to show you 2 ways, first one is basic one and the second one is more complicated one if you need more control.
Simple
This will be straightforward, I'm adding some routes to my SpringBootTest properties, I use WebTestClient utility that provided by Spring to me for Reactive tests agains Netty. Then in my test I just send request to this /test endpoint and expect that it is configured (based on your implementation, if you don't extend spring cloud gateway I can say this test is useless, we should not test spring cloud gateway features, but anyway this is what I understand from your description)
#RunWith(SpringRunner.class)
#SpringBootTest(properties = {
"spring.cloud.gateway.routes[0].id=test",
"spring.cloud.gateway.routes[0].uri=http://localhost:8081",
"spring.cloud.gateway.routes[0].predicates[0]=Path=/test/**",
}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class NettyRoutingFilterTests {
#Autowired
private ApplicationContext context;
#Test
#Ignore
public void mockServerWorks() {
WebTestClient client = WebTestClient.bindToApplicationContext(this.context)
.build();
client.get().uri("/test").exchange().expectStatus().isOk();
}
Complicated
So second way to do it could be; set your mock route locators to the context from your source code and call your services, assert your response. This is different then setting routes from SpringBootProperties when you need some control for some reason (in my case we are using Contract Tests which I'm not going to in details), but here is some mock which I did not try complete example (but the same method in my projects) but it should give you the idea and some starting point;
#ExtendWith( { SpringExtension.class } )
#SpringBootTest(classes = { MockConfigurer.class },
webEnvironment = WebEnvironment.RANDOM_PORT )
public class RoutingIT
{
#LocalServerPort
private int port;
You should mock the routes like following, so this will return our ServiceInstance when requested. In next step we will also put our ServiceInstance to the context. (I'm using discovery client here where my routes are returned from consul/eureka, but important point here is there are RouteDefinitions in the context. If you are using another locater, check RouteDefinitionLocator implementation and inject corresponding routes to your context based on that);
#Configuration
public class MockConfigurer
{
private List<ServiceInstance> services;
public MockConfigurer( List<ServiceInstance> services)
{
this.services= services;
}
#Bean
public DiscoveryClient discoveryClient( )
{
final DiscoveryClient mock = mock( DiscoveryClient.class );
final Map<String, List<ServiceInstance>> clusters =
this.services.stream( ).collect( Collectors.groupingBy( ServiceInstance::getServiceId ) );
given( mock.getServices( ) ).willReturn( new ArrayList<>( clusters.keySet( ) ) );
clusters.forEach( ( clusterId, services ) -> given( mock.getInstances( clusterId ) ).willReturn( services ) );
return mock;
}
}
Now implement a MockService in your tests;
public class MockService implements ServiceInstance
{
// fields, constructors
#Override
public String getServiceId( )
{
return id;
}
#Override
public int getPort( )
{
return port;
}
// and other functions as well, but you will get the point
Create instances of this MockService in your test and inject them to spring context so that they can be discovered our previous MockConfigurer as a service;
#Bean
public static MockService mockClusterInstance1( )
{
return new MockService("test", 8081, // more fields based on your implementation, also pay attention this is what we defined in the #SpringBootTest annotation);
}
Now everything is ready to test.
#Test
public void should_GetResponseFromTest_WhenCalled( ) throws Exception
{
URI uri= new URI( "http://localhost:" + this.port+ "/test");
ResponseEntity<String> res = this.restTemplate.getForEntity( uri, String.class );
assertThat( res.getStatusCodeValue( ) ).isEqualTo( HttpURLConnection.HTTP_OK );
assertThat( res.getBody( ) ).isEqualTo( // your expectation );

Testing camel routes

I have multiple routes classes defined in my project under com.comp.myapp.routes.
For testing these I am mocking the end route and checking/comparing delivery received.
Say for example I have below routes:
public class MyRoute1 extends RouteBuilder {
public void configure() throws Exception {
//Route_1 code
}
}
public class MyRoute2 extends RouteBuilder {
public void configure() throws Exception {
//Route_2 code
}
}
....
...//some route impl
..
public class MyRouteN extends RouteBuilder {
public void configure() throws Exception {
//Route_N code
}
}
Now for all these routes the test case that I wrote seems same.
First mock it.
Mock for MyRoute1:
public class MyRoute1_Mock extends RouteBuilder {
public void configure() throws Exception {
from("direct:sampleInput")
.log("Received Message is ${body} and Headers are ${headers}")
.to("mock:output");
}
}
Test for MyRoute1:
public class MyRoute1_Test extends CamelTestSupport {
#Override
public RoutesBuilder createRouteBuilder() throws Exception {
return new MyRoute1_Mock();
}
#Test
public void sampleMockTest() throws InterruptedException {
String expected="Hello";
/**
* Producer Template.
*/
MockEndpoint mock = getMockEndpoint("mock:output");
mock.expectedBodiesReceived(expected);
String input="Hello";
template.sendBody("direct:sampleInput",input );
assertMockEndpointsSatisfied();
}
}
Now to make unit test for other classes just copy and paste the above code with different name say MyRoute2_Test , MyRoute3_Test , ...MyRouteN_Test.
So what did it actually tested?
It's just written for the purpose of writing test case.
It actually just checks/tests if mock library and camel-test library work or not Not our code works or not?
How should it actually be done?
You want to test your Camel routes but in the test you mock them away. So yes, you are testing your route mock instead of the real route.
To test your real routes:
Send a message to your real routes from endpoint
If this is not easy, mock your from endpoint (not the entire route!) by replacing it with a direct endpoint. This is quite easy with adviceWith
The test message is going through your route
Assert that any to endpoint receives the correct message by mocking these endpoints too. Again, use adviceWith for that. And Camel Mock of course
You can get the received messages (Exchanges) from a Camel Mock to do in depth assertions
If you got the happy test, start to write negative tests by injecting errors in your route. adviceWith can help here too
... and so on
If you are completely new to Camel route tests, get Camel in Action 2nd edition. It explains all mentioned testing aspects for Camel applications on 65 pages. And of course it also takes you on a complete ride through the Camel universe on much more pages.
By the way: if testing your routes is hard, they are too complex. Start to divide your routes so that they are easily testable.
The route you show doesn't really do anything to the messages traversing it, so testing that the same text you sent in one end comes out the other is all there is to test.
For routes with more data transformation and processing, you could test the output data types, that processors were called when needed, you could mock in throwing of exceptions, etc. What you have above is a good start on that.
Explained in-line,Hope this helps you understand significance of Mock in Unit Test:
public void sampleMockTest() throws InterruptedException {
String expected="Hello";
MockEndpoint mock = getMockEndpoint("mock:output");//Mocking endpoint
mock.expectedBodiesReceived(expected); //Setting expected output to mocked endpoint
String input="Hello";
template.sendBody("direct:sampleInput",input );//triggering route execution by sending input to route
assertMockEndpointsSatisfied(); //Verifies if input is equal to output
}
If your endpoint is Rest service you can make use of "TestRestTemplate" instead of Mocking it and Test like below:
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SampleCamelApplicationTest {
}
import org.springframework.boot.test.web.client.TestRestTemplate;
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SampleCamelApplicationTest {
#Autowired
private TestRestTemplate restTemplate;
}
#Test
public void sayHelloTest() {
// Call the REST API
ResponseEntity<String> response = restTemplate.getForEntity("/camel/hello", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
String s = response.getBody();
assertThat(s.equals("Hello World"));
}

Camel adviceWith for ExceptionHandler

I need to test my Camel Exception handler:
#Configuration
public class RouteConfiguration extends RouteBuilder {
#Override
public void configure() throws Exception {
onException(HttpOperationFailedException.class).
handled(true).
log("HttpOperationFailedException: ${exception}").
onExceptionOccurred(myRestExceptionProcessor).id("myRestExceptionProcessor").end();
from("direct:myPrettyRoute").routeId("myPrettyRoute");//lots of routing here
}
}
I'm trying to add adviceWith after myRestExceptionProcessor, but can't find a way.
public class MyExceptionRoutingTest {
#Autowired
private CamelContext context;
#Before
public void before() throws Exception {
if (ServiceStatus.Stopped.equals(context.getStatus())) {
log.info("prepare mocks endpoint");
List<OnExceptionDefinition> ed = context.getErrorHandlerBuilder().getErrorHandlers(context.getRoutes().get(0).getRouteContext());
//FAILS, because context.getRoutes() is empty at the moment
//even if it wasn't, getErrorHandlerBuilder() is deprecated
}
}
}
I need to add something like this for the exceptionHandler definition:
.adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
weaveById("myExceptionProcessor").after().to(myResultEndpoint).id("myResponseEndpoint");
}
});
Is it possible?
I don't fully understand if you want to test your error handler (onException block) or just your myRestExceptionProcessor, but from a Camel perspective these are two kinds of tests:
Routing-Tests to test your routing logic and make sure that messages are correctly routed under various conditions that could happen in the route. This is the kind of tests you write with the Camel Testkit (that offers adviceWith and much more).
Classic unit tests to test an isolated Bean, Processor or anything else that is used in the route to implement business logic. This kind of test is done with JUnit, TestNG or other classic unit test frameworks, it has nothing to do with Camel. Do not try to test such components with Camel Route tests since it is much more complicated than in a unit test!
So, if you want to test your routing when an error occurs you throw the needed error in your route test to trigger the error handler. If you use a dependency injection framework like Spring this is easy since you can inject a test Bean that throws an error instead of a real Bean used in the route.
To add a Mock endpoint at the end of a route, use adviceWith
.adviceWith(camelContext, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
weaveAddLast().to("mock:error");
}
}
Hope this helps a bit. Feel free to extend your question to elaborate your problem a bit more.
I've solved the trick as follows, without changing the route:
//entry point of the route is invoked here
Exchange send = myProducer.withBody("body is here").send();
HttpOperationFailedException exception = send.getException(HttpOperationFailedException.class);
String responseBody = exception.getResponseBody();
//recieved result and made assertions
assert responseBody != null; // any other assertions

junit test case for spring MVC

we are developing an application using spring mvc framework. I have given all the classes below, please suggest how to write a junit test case for the below scenario.
I want to write a junit test case for the validateAccountInformation(requestDTO) method which is called in validateAccount(..) method of LPAValidator.java class. Below is my junit test case followed by the java classes. Actual call goes from the LPAController.java as shown in the below code.
LPAControllerTest.java
#Test(groups = "manual")
public void submitRequestForLPAAccountTest()
{
// businessCalendar.nextBusinessDay(
// LocalDateHelper.today(), LPAConstants.TWENTY_BUSSINESS_DAYS)
//i want to write the test case for the above commented logic,
//if business days is not equal to twenty days, test should fail.
}
LPAController.java
#RequestMapping(value = "/lpa/{accNumber}/spread, method = RequestMethod.GET)
public #ResponseBody LPAResponseDTO accountSearch(#RequestBody final LPARequestDTO clientrequestBody,
#PathVariable final String accNumber, final HttpServletResponse response)
{
//some logic goes here
final LPAAccountResponse domainResponse = service.submitRequestForLPAAccount(requestBody);
}
LPAServiceImpl.java
#PermitAll
#NonTransactional
public LPAResponse submitRequestForLPAAccount(final LPARequest requestDTO)
{
return lpaRepository.submitRequestForLPAAccount(requestDTO));
}
LPARepository.java
#PermitAll
#NonTransactional
public LPAResponse submitRequestForLPAAccount(final LPARequest requestDTO)
{
//some logic
lpaValidator.validateAccount(requestDTO);
//some logic
}
LPAValidator.java -- java class for validations
#component
class LPAValidator{
#Inject
private BusinessCalendar businessCalendar;
void validateAccount(final LPARequest requestDTO) throws Exception {
try {
validateAccountInformation(requestDTO);
} catch(Exception e){
}
}
private void validateAccountInformation(final LPARequest requestDTO) throws Exception{
final accDate lpaAccDate = requestDTO.getLPADate();
final LocalDate twentyBussinessDays = businessCalendar.nextBusinessDay(
LocalDateHelper.today(), LPAConstants.TWENTY_BUSSINESS_DAYS); //i want to write
//test case for this line of code, if business days given is more than twenty test should fail.
//some logic here
}
Please suggest what needs to be added in LPAControllerTest.java to test the nextBusinessDay(..) as discussed above.
You're trying to write an integration test in which your controller is called, which then calls all the subclasses until the validator is triggered. That is not a traditional 'unit test'.
A traditional unit test would just test the validator straight up, and nothing more.
Nevertheless, when writing an integration test, spring documentation to the rescue
In short, it'll require you to create an applicationcontext with all the necessary scaffolding, and then use a mockMvc call to do a GET on the created application.
If you want to test the validator, use simple mocking framework:
See [http://mockito.org]
Gives you something like this:
#Mock BusinessCalendar businessCalendarMock;
#Mock LPARequest mockRequest;
#Mock accDate mockDate;
#Mock LocalDate mockLocalDate;
#InjectMocks LPAValidator lpaValidator = new LPAValidator();
#Test public void testValidateAccount() {
when(mockRequest.getLPAdate()).thenReturn(mockDate);
when(businessCalendar.nextBusinessDay(LocalDateHelper.today(),LPAConstants.TWENTY_BUSSINESS_DAYS).thenReturn(mockLocalDate);
// continue your test from here
lpaValidator.validateAccount( mockRequest);
verify(businessCalendar).nextBusinessDay(LocalDateHelper.today(),LPAConstants.TWENTY_BUSSINESS_DAYS);
// although if the use of mockLocalDate is integral to your code, it'll probably show before and no verify is necessary;

Categories

Resources