How to invoke a retry of resilience4j on a spring mono in a spring-reactor and resilience4j when not using annotations - java

I'm calling a service class method using the transformDeferred method:
public Mono<SPathResponse> getPath(SPathRequest request) {
return pathService.getPath(request)
.transformDeferred(RetryOperator.of(retry));
}
My retry configuration:
Retry retry = Retry.of("es", RetryConfig.custom()
.maxAttempts(3)
.waitDuration(Duration.of(30, ChronoUnit.MILLIS))
.writableStackTraceEnabled(true)
.failAfterMaxAttempts(true)
.retryOnException(throwable -> throwable instanceof RuntimeException)
.build());
and the test method:
#Test
void shouldRetry() {
BDDMockito.given(pathService.getPath(any(SPathRequest.class)))
.willReturn(Mono.error(RuntimeException::new))
.willReturn(Mono.just(SPathResponse.builder().build()));
cachedPathService.getPath(SPathRequest.builder()
.sourceNodeId("2")
.sourceCategoryId("1234")
.destinationNodeId("123")
.build())
.as(StepVerifier::create)
.expectError(RuntimeException.class)
.verify();
var captor = ArgumentCaptor.forClass(SPathRequest.class);
BDDMockito.then(pathService).should(times(2)).getPath(captor.capture());
Running it, I do get the expected exception, but 'getPath' is invoked only once.
I probably miss something cause the retry mechanism should have retried and return the stubbed result on the 2nd invocation which should fail the test since no exception occurred and the actual result should have been expected.
What is wrong with my configuration?
Edit:
I want the equivalent of this snippet (from resilience4j-reactor examples) for directly invoking on my Mono rather then wrapping a function with Mono.fromCallable):
#Test
public void returnOnErrorUsingMono() {
RetryConfig config = retryConfig();
Retry retry = Retry.of("testName", config);
RetryOperator<String> retryOperator = RetryOperator.of(retry);
given(helloWorldService.returnHelloWorld())
.willThrow(new HelloWorldException());
StepVerifier.create(Mono.fromCallable(helloWorldService::returnHelloWorld)
.transformDeferred(retryOperator))
.expectSubscription()
.expectError(HelloWorldException.class)
.verify(Duration.ofSeconds(1));
StepVerifier.create(Mono.fromCallable(helloWorldService::returnHelloWorld)
.transformDeferred(retryOperator))
.expectSubscription()
.expectError(HelloWorldException.class)
.verify(Duration.ofSeconds(1));
then(helloWorldService).should(times(6)).returnHelloWorld();
Retry.Metrics metrics = retry.getMetrics();
assertThat(metrics.getNumberOfFailedCallsWithRetryAttempt()).isEqualTo(2);
assertThat(metrics.getNumberOfFailedCallsWithoutRetryAttempt()).isEqualTo(0);
}
Where retryConfig is defined like this:
private RetryConfig retryConfig() {
return RetryConfig.custom()
.waitDuration(Duration.ofMillis(10))
.build();
}
Thanks.

Related

Resilience4j Not Ignoring Exceptions

JobServiceImpl.java:
#CircuitBreaker(name = "jobsApiServiceGetAllJobs", fallbackMethod = "getAllJobsFallback")
public ResponseEntity<JobsResponse> getAllJobs() {
...
throw new ApiException();
}
public ResponseEntity<JobsResponse> getAllJobsFallback(Throwable throwable) {
log.error("Fallback method called");
}
application.properties:
resilience4j.circuitbreaker.instances.jobsApiServiceGetAllJobs.ignoreExceptions=ccp.shared.platform.exception.ApiException,ccp.shared.platform.exception.BadRequestApiException
Whenever ccp.shared.platform.exception.ApiException is thrown, the fallback method is called even though I have added it in the ignoreExceptions list in the application.properties file. I want it to not trigger the fallback method when ApiException is thrown. I have tried similar questions on stack overflow and those does not work for me. Am I doing anything wrong?
Seems that the property is defined in the docs but actually, it does not work. There is a pull request for that -> CircuitBreakerConfigurationProperties has no ignoreException property.
You can use the common Spring+java configuration to achieve that in the meantime:
#Bean
CircuitBreaker reportingApiCircuitBreaker(CircuitBreakerRegistry registry) {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.ignoreExceptions(ApiException.class, BadRequestApiException.class)
.build();
return registry.circuitBreaker("yourCircuitBreakername", config);
}

Apache Camel - Definition has no children on pipeline

This is my simple route:
#Override
public void configure() {
onException(Exception.class)
.handled(true)
.process(exchange -> {
Exception exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
log.error(logFormat.error(exchange, exception, exception.getMessage()));
exchange.getIn().setBody(exception.getMessage());
exchange.getIn().setHeader(Exchange.HTTP_RESPONSE_CODE, HttpStatus.SC_INTERNAL_SERVER_ERROR);
});
onException(HttpOperationFailedException.class)
.handled(true)
.to("direct:handleHttpOperationFailed");
from("direct:myRoute")
.routeId("myRoute")
.log("Route start: myRoute")
.id("startStep")
.process(
exchange -> log.info("Doing some processing")
)
.id("processingStep")
.end();
}
Now I want to write some tests:
exception handling case - exception is thrown after "processingStep" and handled in onException
positive scenario - all flow is passing without exceptions
And this is what I have done...
My test annotations:
#RunWith(CamelSpringBootRunner.class)
#UseAdviceWith
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class StarlingFpsInboundWebhookIT {
...
}
My two test cases:
With exception scenario - here I use adviceWith to throw exception after "processingStep":
#Test
public void exceptionScenario() throws Exception {
// Given
context.getRouteDefinition("myRoute").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() {
weaveById("processingStep").after()
.process(exchange -> {
throw ExceptionFixtures.createSimpleException();
})
.id("exceptionThrowStep");
}
});
context.start();
String requestBody = ResourcesHelper.read(this.getClass(), "/data/input/request.json");
// When
template.send("direct:myRoute", camelExchange -> {...});
}
Success case - here I need to delete previously created "exceptionThrowStep" to not to throw Exception:
#Test
public void successScenario() throws Exception {
// Given
context.getRouteDefinition("myRoute").adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() {
weaveById("exceptionThrowStep").remove();
}
});
context.start();
String requestBody = ResourcesHelper.read(this.getClass(), "/data/input/request.json");
// When
template.send("direct:myRoute", camelExchange -> {...});
}
And when I run those tests (first exception then successful scenario) I get exception in second test:
org.apache.camel.FailedToCreateRouteException:
Failed to create route myRoute at:
>>> pipeline -> [[]] <<<
in route: Route(myRoute)[[From[direct:myRoute]...
because of Definition has no children on pipeline -> [[]]
Why this exception occurs? I have read many articles but nothing precious found about this case :(
What is interesting - when in second test in adviceWith I use replace() instead of remove() everything is working...
My question are:
Is is good approach to test exception handling in route?
Can we reset the route definition between tests to avoid my approach?
I start with your two questions at the end:
Yes, it is good practice to also test the error path of your routes, not only the happy path
Yes, you can and should reset the route definition between tests. Since you run SpringBootTests you can do it with the DirtiesContext annotation (part of Spring Test Framework)
Example:
#Test
#DirtiesContext
public void successScenario() throws Exception {
This resets the Camel route by default after the annotated test has been run.
Your tests must not depend on a specific order to run successful! This leads to flaky test results and lowers your confidence in your tests.
In general your success tests should not need any route modifications since this is the expected processing path. For the error tests you have to inject errors in your routes (as you do it with adviceWith) to simulate processing errors.

How can i unit test a method that contains vertx().eventBus().send()?

Frustrating. Everywhere i look, i see samples of testing async Vertx code, but nothing that comes close to what i am trying to test.
Vertx 3.3.2, JUnit 4.12, Java 8
The method under test sends a message to the event bus. I want to verify that what happens in the eventBus().send() response handler is happening.
Sooooooo many examples i see have the eventBus().send() method in the TEST ITSELF (thus, testing the other end of the event bus - the consumer) I want to test the response handler in the .send()
I have tried Async in the test. Tried ArgumentCaptor. Tried Thread.sleep(). Tried doAnswer(). Nothing seems to get the test to (a) wait for the async eventBus().send() call in the method under test to finish and (b) able to verify() that there was an interaction (i think this might have to do with the different between the Vertx.TestContext and the JUnit.Runner Context..)
Code:
Method under test:
public void sendToEventBusAddress(RoutingContext context, String requestId, String userId) {
List<String> stuff = split(context.request().getParam("stuffToSplit"));
JsonObject eventBusMessage = new JsonObject()
.put("requestId", requestId)
.put("stuffList", new JsonArray(stuff))
.put("userId", userId);
LOGGER.info("Putting message: {} onto the EventBus at address: {}", eventBusMessage.encodePrettily(), EventBusEndpointEnum.STUFF_ACCESS.getValue());
context.vertx().eventBus().send(EventBusEndpointEnum.STUFF_ACCESS.getValue(), eventBusMessage, new DeliveryOptions().setSendTimeout(timeout), async -> {
if (async.succeeded()) {
LOGGER.info("EventBus Response: {}", async.result().body().toString());
context.response().setStatusCode(HttpStatus.SC_OK);
context.response().headers().set(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN);
context.response().end(async.result().body().toString());
} else {
LOGGER.error(errorMessage);
context.response().setStatusCode(HttpStatus.SC_BAD_REQUEST);
context.response().end(errorMessage);
}
});
}
Simplified (non-working) Test case and class:
#RunWith(VertxUnitRunner.class)
public class MyBrokenTest {
#Mock private RoutingContext routingContext;
#Mock private HttpServerRequest contextRequest;
#Mock private HttpServerResponse contextResponse;
#Mock private MultiMap responseHeaders;
#Rule public RunTestOnContext rule = new RunTestOnContext();
#Before
public void setUp(TestContext context) {
MockitoAnnotations.initMocks(this);
}
#Test
public void testOne(TestContext context) {
when(routingContext.vertx()).thenReturn(rule.vertx());
when(routingContext.request()).thenReturn(contextRequest);
when(contextRequest.getParam("stuffToSplit")).thenReturn("04MA");
when(routingContext.response()).thenReturn(contextResponse);
when(contextResponse.headers()).thenReturn(responseHeaders);
rule.vertx().eventBus().consumer(EventBusEndpointEnum.STUFF_ACCESS.getValue(), res -> {
res.reply("yo");
});
ClassUnderTest cut= new ClassUnderTest(180000);
cut.sendToEventBusAddress(routingContext, "testRequestId", "UnitTestId");
verify(contextResponse).setStatusCode(200);
}
}
I know that the test in its current form won't work, because the method under test returns as soon as the eventBus().send() method is called inside the method, and therefore, the verify fails with 'no interactions'.
What i can't figure out, is how to verify it properly given the async nature of Vertx!
Thanks
I did it so:
At #BeforeAll annotated method I deploy only the sending verticle.
At #BeforeEach - I create a consumer for the given message and store message(s) to variable/collection
Something like:
receivedMessage = new Message[1];
eventBus.consumer("DB",
message -> {
message.reply("OK");
receivedMessage[0] = message;
});
context.completeNow();
In test I validate stored value(s):
client.get(8080, "localhost", "/user/" + id)
.as(BodyCodec.string())
.send(context.succeeding((response -> context.verify(() -> {
Assertions.assertEquals(expectedMessage, receivedMessage[0].body());
context.completeNow();
}))));

Mocking a Vertx.io async handler

when I was sync I wrote unit tests mocking the persistence part and check the caller's behavior. Here is an example about what I usually did:
#Mock
private OfferPersistenceServiceImpl persistenceService;
#Inject
#InjectMocks
private OfferServiceImpl offerService;
...
#Test
public void createInvalidOffer() {
offer = new Offer(null, null, null, null, null, 4, 200D, 90D);
String expectedMessage = Offer.class.getName() + " is not valid: " + offer.toString();
Mockito.when(persistenceService.create(offer)).thenThrow(new IllegalArgumentException(expectedMessage));
Response response = offerService.create(offer);
Mockito.verify(persistenceService, Mockito.times(1)).create(offer);
Assert.assertEquals(INVALID_INPUT, response.getStatus());
String actualMessage = response.getEntity().toString();
Assert.assertEquals(expectedMessage, actualMessage);
}
But now I fell in love with Vertx.io (to which I am pretty new) and I want to be async. Nice. But Vertx has handlers, so the new persistence component to mock looks like this:
...
mongoClient.insert(COLLECTION, offer, h-> {
...
});
So I am guessing how to mock handler h to tests class who's using that mongoClient or even if it is the right way to test with Vertx.io. I am using vertx.io 3.5.0, junit 4.12 and mockito 2.13.0. Thanks.
Update
I tried to follow tsegimond suggestion but I can't get how Mockito's Answer and ArgumentCaptor can help me. Here is what I tried so far.
Using ArgumentCaptor:
JsonObject offer = Mockito.mock(JsonObject.class);
Mockito.when(msg.body()).thenReturn(offer);
Mockito.doNothing().when(offerMongo).validate(offer);
RuntimeException rex = new RuntimeException("some message");
...
ArgumentCaptor<Handler<AsyncResult<String>>> handlerCaptor =
ArgumentCaptor.forClass(Handler.class);
ArgumentCaptor<AsyncResult<String>> asyncResultCaptor =
ArgumentCaptor.forClass(AsyncResult.class);
offerMongo.create(msg);
Mockito.verify(mongoClient,
Mockito.times(1)).insert(Mockito.anyString(), Mockito.any(), handlerCaptor.capture());
Mockito.verify(handlerCaptor.getValue(),
Mockito.times(1)).handle(asyncResultCaptor.capture());
Mockito.when(asyncResultCaptor.getValue().succeeded()).thenReturn(false);
Mockito.when(asyncResultCaptor.getValue().cause()).thenReturn(rex);
Assert.assertEquals(Json.encode(rex), msg.body().encode());
and using Answer:
ArgumentCaptor<AsyncResult<String>> handlerCaptor =
ArgumentCaptor.forClass(AsyncResult.class);
AsyncResult<String> result = Mockito.mock(AsyncResult.class);
Mockito.when(result.succeeded()).thenReturn(true);
Mockito.when(result.cause()).thenReturn(rex);
Mockito.doAnswer(new Answer<MongoClient>() {
#Override
public MongoClient answer(InvocationOnMock invocation) throws Throwable {
((Handler<AsyncResult<String>>)
invocation.getArguments()[2]).handle(handlerCaptor.capture());
return null;
}
}).when(mongoClient).insert(Mockito.anyString(), Mockito.any(),
Mockito.any());
userMongo.create(msg);
Assert.assertEquals(Json.encode(rex), msg.body().encode());
And now I got confused. Is there a way to mock an AsyncResult to let it return false on succeed()?
Finally I got some times to investigate and I made it. Here is my solution.
#RunWith(PowerMockRunner.class)
#PowerMockRunnerDelegate(VertxUnitRunner.class)
#PrepareForTest({ MongoClient.class })
public class PersistenceTest {
private MongoClient mongo;
private Vertx vertx;
#Before
public void initSingleTest(TestContext ctx) throws Exception {
vertx = Vertx.vertx();
mongo = Mockito.mock(MongoClient.class);
PowerMockito.mockStatic(MongoClient.class);
PowerMockito.when(MongoClient.createShared(Mockito.any(), Mockito.any())).thenReturn(mongo);
vertx.deployVerticle(Persistence.class, new DeploymentOptions(), ctx.asyncAssertSuccess());
}
#SuppressWarnings("unchecked")
#Test
public void loadSomeDocs(TestContext ctx) {
Doc expected = new Doc();
expected.setName("report");
expected.setPreview("loremipsum");
Message<JsonObject> msg = Mockito.mock(Message.class);
Mockito.when(msg.body()).thenReturn(JsonObject.mapFrom(expected));
JsonObject result = new JsonObject().put("name", "report").put("preview", "loremipsum");
AsyncResult<JsonObject> asyncResult = Mockito.mock(AsyncResult.class);
Mockito.when(asyncResult.succeeded()).thenReturn(true);
Mockito.when(asyncResult.result()).thenReturn(result);
Mockito.doAnswer(new Answer<AsyncResult<JsonObject>>() {
#Override
public AsyncResult<JsonObject> answer(InvocationOnMock arg0) throws Throwable {
((Handler<AsyncResult<JsonObject>>) arg0.getArgument(3)).handle(asyncResult);
return null;
}
}).when(mongo).findOne(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
Async async = ctx.async();
vertx.eventBus().send("persistence", new JsonObject(), msgh -> {
if (msgh.failed()) {
System.out.println(msgh.cause().getMessage());
}
ctx.assertTrue(msgh.succeeded());
ctx.assertEquals(expected, Json.decodeValue(msgh.result().body().toString(), Doc.class));
async.complete();
});
async.await();
}
}
Use Powemockito to mock the MongoClient.createShared static method, so you'll have your mock when verticle starts. Mocking async handler is a bit of code to write. As you can see mocking start at Message<JsonObject> msg = Mockito.mock(Message.class); and ends at Mockito.doAnswer(new Answer.... In the Answer's method pick the handler param and force it to handle your async result then you're done.
Normally, I'd use a comment to post this, but formatting gets lost. The accepted solution is works great, just note that it can be simplified a bit using Java 8+, and you can use your actual objects instead of JSON.
doAnswer((Answer<AsyncResult<List<Sample>>>) arguments -> {
((Handler<AsyncResult<List<Sample>>>) arguments.getArgument(1)).handle(asyncResult);
return null;
}).when(sampleService).findSamplesBySampleFilter(any(), any());
getArgument(1), refers to the index of the handler argument in a method such as:
#Fluent
#Nonnull
SampleService findSamplesBySampleFilter(#Nonnull final SampleFilter sampleFilter,
#Nonnull final Handler<AsyncResult<List<Sample>>> resultHandler);

Can we use #Async on a #HystrixCommand

I don't know how to test this and need a quick answer whether it is possible, and how could I possibly test this.
So i have an api-gateway call from a service, and what i want to know is that if following is possible with spring boot
#Component
class GatewayService {
#Autowired
private MicroserviceGateway microServiceGateway;
#Async
#HystrixCommand(threadPool = "microservice", groupKey = "microService", fallback = "getResponse2")
private Future<ResponseDTO> getResponse(RequestDTO request) {
try {
ResponseDTO response = new APIRequest(endpoint, api).body(request).post();
return new AsyncResult(response);
} catch (Exception e) {
throw new GatewayServiceException(e);
}
}
public Future<ResponseDTO> getResponse2(RequestDTO request) {
return Futures.immediateFuture(RequestDTO.empty());
}
}
Will the fallback work? will it all be async?
EDIT : Tried this, and hystrix is being ignored, since the future is returned immediately. Any work arounds? solutions?
I believe the approach to use Hystrix commands asynchronously using a thread pool would be:
If using #HystrixCommand annotation:
...
#HystrixCommand
public Future<Product> findProduct(String id) {
return new AsyncResult<Product>() {
#Override
public Product invoke() {
...
return productService.find ...
}
};
}
...
If using a Hystrix command class, for instance ProductCommandFind.java
...
ProductCommandFind command = new ProductCommandFind(...);
Future<Product> result = command.queue();
...
Hystrix command uses its own and configurable threadpool, maybe configuring a thread pool per command group, lets say a thread pool for command group named PRODUCT_GROUP used by commands: productCommandFind, productCommandCreate, ....
Basically I don't think there is a need to annotate a method with #Async and #HystrixCommand at the same time.
This should work. Your normal DTO method will be executed async and during fallback it will be synchronous.

Categories

Resources