Unit testing an API? - java

I have a class and a method which looks like this
class A {
private RetryLogic logic;
private Service serviceClient;
public A(){
logic = new RetryLogic();
serviceClient = new Service();
}
public Response methodA() {
Request request = new Request();
serviceClient.addRetryLogic(logic);
Responce response = serviceClient.call(request);
return response;
}
}
The retry logic in this case will retry the service call if there is a failure like Service not available or any HTTP error.
But it won't retry if the service is getting called and throwing any modeled exceptions.
If I am writing a unit test for methodA is it appropriate (or should I be concerned) to test the retry logic by adding a test which would mock an HTTP exception and check if the retry works ?
The RetryLogic is basically a different package not owned by me.

Would you test the setConnectionTimeout of the Apache Commons HttpClient? or the setAutoCommit of your JDBC Client? I wouldn't.
If you trust that Service as you trust the rest of your dependencies, it makes no sense to test it. Just imagine that there are 1000 developers using Service, and all of them are testing the retry functionality... it's a bit of waste of time, isn't it?

Related

Test service with 3rd party API calls

I have a service with business logic and http calls (using Unirest) to a third party service.
public PaymentLinkResult generatePaymentLink(PaymentLinkParams paymentLinkParams) throws GatewayException {
// BUSINESS LOGIC
HttpResponse<JsonNode> response;
try (UnirestInstance service = Unirest.spawnInstance()) {
response = service.post(apiBaseUrl + "/api/v1/openOrder.do")
.body(body)
.asJson();
}
if (response.getBody() == null) {
// BUSINESS LOGIC
}
// BUSINESS LOGIC
}
I created a test for this service, my goal is to test the business logic without making a real HTTP call to the 3rd party service. Basically i want to test if the request is created correctly and if the output is correct.
I'm struggling to find the best way to handle this scenario with Spring Boot. I know how to mock the entire method but i would like to mock only the HTTP call part.

Wiremock does not respond

I have an integration test for rest api microservers that stubs some of the external resources. Most of them work, but there is a particular one which becomes not available during the test.
Other tests stubbed the same way work
Given stubbing works when request is sent directly from the test or if called from postman while test is in Thread.sleep()
Stubbing does not work when the same request as used above is sent from one of the microservices called by the test or from postman if a microservice is paused (paused on a debug breakpoint). Other stubs work in these conditions.
#ClassRule
public static WireMockRule wireMockRule = new WireMockRule(
wireMockConfig()
.port(8080)
.usingFilesUnderDirectory("src/integration-test/resources"));
public static void stubExtService(WireMockRule wireMockRule) {
wireMockRule.stubFor(post(urlPathEqualTo("/my/url"))
.willReturn(aResponse().withHeader("Content-Type", "application/json")
.withStatus(200)
.withBodyFile("json/response.json")));
}
It sounds like the issue is that the test is finishing and WireMock shutting down before the async operation has completed. In this situation you can use Awaitility to wait (polling) for an expected request.
Here's an example in WireMock's tests:
https://github.com/tomakehurst/wiremock/blob/master/src/test/java/com/github/tomakehurst/wiremock/PostServeActionExtensionTest.java#L85
Although I have not located it in the code yet, my entry point service was responding and then processing the request asynchronously. Meanwhile, the test was receiving the response and shutting down along with WireMock, and microservices could not access the mocked resource any longer.

Unit Test Apache-Camel JmsReplyTo route flow

I have the following sample route flow, where I receive a jms message, built a webservice request and then respond to the JMSReplyTo with the webservice response:
from("{{jms.input.queue}}).routeId("Receive JMS Message")
.to("direct:start");
from("direct:start").routeId("Build & Send Http Request")
.bean(processRequest)
.to("{{http.endpoint}}")
.to("direct:processResponse");
from("direct:processResponse").routeId("Build XML Response")
.convertBodyTo(String.class)
.bean(processResponse);
I have successfuly unit tested my processes, but now I want to unit test my route flow. Instead of having a EMS server running during tests, I have started from the second route:
camelContext.getRouteDefinition("Build & Send Http Request").adviceWith(camelContext,
new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
interceptSendToEndpoint("http://*")
.skipSendToOriginalEndpoint()
.setBody("Hello");
}
});
#Test
#DirtiesContext
public void RouteFlowTest() throws Exception{
Map<String,Object> jmsHeaders = new HashMap<>();
jmsHeaders.put("Auth","helloWorld");
jmsHeaders.put("JMSReplyTo","sample");
String jmsBody = "Help Me"
incomingJmsRequestMessage.sendBodyAndHeaders("direct:start", jmsBody, jmsHeaders);
}
but now how to I assert the exchange after the processResponse bean has been executed?
Or is there a way to test from the first route and satisfy the JMSReplyTo without actually having a EMS server running?
As you are already weaving the route, you'd could add a propagation to a mock endpoint within your route advice such as:
this.weaveAddLast().to("mock:done");
which just adds a .to("mock:done") defintion to the end of your "Build & Send Http Request" route. From the given problem statement it is a bit unclear what .bean(processResponse); actually does. You could also add this mock endpoint propagation to the "Build XML Response" route in which case you'd need a further route advice definition.
Next, you can either let Camel inject a mock endpoint via
#EndpointInject(uri = "mock:done")
private MockEndpoint done;
or define it manually inside your test via:
MockEndpoint done = camelContext.getEndpoint("mock:done", MockEndpoint.class);
This mock endpoint can be used to define certain expectations such that you'd expect one message to be received by that endpoint
done.expectedMessageCount(1);
...
// invoke your route here
template.sendBody(...);
...
done.assertIsSatisfied();
You can also access the exchanges received by this endpoint directly via the following direction and perform further assertions on it:
Exchange exchange = done.getExchanges().get(0);
...
If you'r using Camel on top of Spring (Boot) you might also read through how to test Camel with Spring enabled

Feign: Retry depending on response status

I am currently using Spring Cloud and Feign to consume a Microservice in my application. Since it can happen, that a database connection or the like fails in a single service instance, making it return 500 HTTP status code, I want to make sure, that the next server is retried by the service's clients. Currently, Ribbon's retry mechanism works like a charm when the service is not running at all, however it still returns instantly an error when it receives a 500 status code, without any retry.
Is it possible to configure the Feign clients or their underlying Ribbon load balancers to retry the next server, if an instance returns a 500 response?
The configuration is pretty much the same as in this thread: Does Feign retry require some sort of configuration?
I would love to use an implementation like Ribbons' HttpResponseValidator (https://github.com/Netflix/ribbon/blob/master/ribbon/src/main/java/com/netflix/ribbon/http/HttpResponseValidator.java), but I couldn't find anything usable for Spring Cloud and its Feign/Ribbon integration
This question is very old and the solution was probably already found or wasn't possible at the time. Anyway, I think that answer might still help someone 8 ).
Please use this as a reference, this code is not intended for production use.
Feign allows you to configure errorDecoder - this is the place where magic happens.
Feign.Builder builder = Feign.builder()
.errorDecoder(new RetryOnScaleErrorDecoder())
Here is the implementation, I use that class to retry request on HTTP error 429 I get from AWS when service is scaling
public static class RetryOnScaleErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
FeignException exception = errorStatus(methodKey, response);
// This is a terrible part please check how feign.codec.ErrorDecoder.RetryAfterDecoder is implemented for proper parsing of retry-after header
Collection<String> headers = response.headers().get("Retry-After");
String repeatAfterString = "0";
if (Objects.nonNull(headers)) {
repeatAfterString = Iterables.getFirst(headers, "0");
}
assert repeatAfterString != null;
Date repeatAfter = new Date(currentTimeMillis());
if (repeatAfterString.matches("^[0-9]+$")) {
try {
long deltaMillis = SECONDS.toMillis(Long.parseLong(repeatAfterString));
repeatAfter = new Date(currentTimeMillis() + deltaMillis);
} catch (NumberFormatException ignored) {
// TODO: logging
}
}
// That's the part where we decide to retry based on status code value
if (exception.status() == 429) {
return new RetryableException(
response.status(),
exception.getMessage(),
response.request().httpMethod(),
exception,
repeatAfter
);
}
return exception;
}
}
I think that in conjunction with Ribbon it will produce desired result.
Try to this config:
MY-SPRING-API.ribbon.retryableStatusCodes=404,500
This is the same question:
Feign client and Spring retry
document is :
https://docs.spring.io/spring-cloud-netflix/docs/2.2.10.RELEASE/reference/html/#retrying-failed-requests

How do I unit test code which calls the Jersey Client API?

I wrote code which calls the Jersey client API which in turn calls a web service which is out of my control. I do not want my unit test to call the actual web service.
What is the best approach for writing a unit test for code which calls the Jersey client API? Should I use the Jersey server API to write a JAX-RS web service and then use the Jersey Test Framework for the unit test? Or should I mock out the Jersey web service calls? I have access to JMock. Or should I try another approach?
During my research, I found this discussion describing various options, but I did find a complete solution. Are there any code examples available showing a suggested JUnit approach? I could not find any in the Jersey documentation.
Here is the relevant source code:
public String getResult(URI uri) throws Exception {
// error handling code removed for clarity
ClientConfig clientConfig = new DefaultClientConfig();
Client client = Client.create(clientConfig);
WebResource service = client.resource(uri);
String result = service.accept(accept).get(String.class);
return result;
}
Here are examples of test code I would like to pass. I would like to test (1) passing in a valid URI and getting a valid string back and (2) passing in an invalid (for whatever reason -- unreachable or unauthorized) URI and getting an exception back.
#Test
public void testGetResult_ValidUri() throws Exception {
String xml = retriever.getResult(VALID_URI);
Assert.assertFalse(StringUtils.isBlank(xml));
}
#Test(expected = IllegalArgumentException.class)
public void testGetResult_InvalidUri() throws Exception {
retriever.getResult(INVALID_URI);
}
Everything above is the simple description of what my code does. In reality, there is a layer on top of that that accepts two URIs, first tries calling the first URI, and if that URI fails then it tries calling the second URI. I would like to have unit tests covering (1) the first URI succeeds, (2) the first URI fails and the second URI succeeds, and (3) both URIs fail. This code is sufficiently complex that I want to test these different scenarios using JUnit, but to do this I either need to run actual stand-in web services or mock out the Jersey client API calls.
Try to use Mockito or Easymock for mocking service calls. You need to mock only these methods which are actually used - no need to mock every method. You can creat mock object for WebResource class, then mock accept method call.
In #BeforeClass/#Before JUnit test method write something like (Mockito example)
WebResource res = mock(WebResource.class);
when(res.accept(something)).thenReturn(thatWhatYouWant);
Then in your tests you can use res object as if it was real object and call mock method on it. Instead of returning value you can also throw exceptions. Mockito is pretty cool.
Typically what you are really after is "does the way I use the Jersey Client DSL produce a request to the correct URL with the correct payload and URL parameters". Testing this with Mockito is really verbose and the setup code will usually end up looking something like this:
when(authentication.queryParam(eq("sa"), anyBoolean())).thenReturn(testAuthentication);
when(testAuthentication.resolveTemplate("channel", "smf")).thenReturn(testAuthentication);
when(testAuthentication.request(
MediaType.APPLICATION_JSON_TYPE)).thenReturn(mockRequestBuilder);
when(mockRequestBuilder.post(any(Entity.class))).thenReturn(mockResponse);
when(mockResponse.readEntity(ResponseWrapper.class)).thenReturn(successfulAuthResponse());
And this is basically just for a single REST request. It's overly verbose, and instead of testing the hoped outcome you are just replicating the steps you think are correct in using the Jersey Client DSL.
Instead of the above, I would aim for mocking a simple service. For this I've used WireMock which starts a Jetty server and where I can stub things like "expect a request to this URL, respond with this message and verify that the payload is this".
I know this is edging on an integration test and it is a bit slower than just using Mockito but I value testing the real outcome and I value the readability of the tests way more in this case.
Setup for a WireMock based Jersey Client test looks something like this:
#Test
public void exactUrlOnly() {
stubFor(get(urlEqualTo("/some/thing"))
.willReturn(aResponse()
.withHeader("Content-Type", "text/plain")
.withBody("Hello world!")));
assertThat(testClient.get("/some/thing").statusCode(), is(200));
assertThat(testClient.get("/some/thing/else").statusCode(), is(404));
}
Just implement a work-alike service and in your unit test setup start the service using HttpServerFactory.

Categories

Resources