Mocking nested retrofit api calls using MockWebServer - java

I am writing a junit test using okhttp3.mockwebserver for a retrofit2 rest api.
The trimmed down api looks like this:
public interface MyApi{
#POST("/api/get-orders")
retrofit2.Response<Set<String>> getOrders();
#POST("/api/cxl-order")
retrofit2.Response<String> cancelOrder(String ordeId);
}
The api is then injected to another class which delegates the calls thusly:
public class MyExchange{
private final MyApi api;
public MyExchange(MyApi api){
this.api = api;
}
public final Set<String> getOrders(){
Response<Set<String>> resp = api.getOrders();
//parse the response
Set<String> result = parse( resp );
return result;
}
public final boolean cancelOrder( String orderId ){
api.cancelOrder( orderId );
//Nested Call
Set<String> orders = getOrders();
return !orders.contains(orderId);
}
}
I do the following in my test:
#Test
public void cancel_order(){
MockWebServer server = new MockWebServer();
server.start();
String orderId ="OrderId_123";
MyApi mockApi = new Retrofit.Builder().baseUrl("/").build().create(MyApi.class);
MyExchange exchange = new MyExchange(mockApi);
server.enqueue( new MockResponse().setResponseCode(HttpURLConnection.HTTP_OK, orderId));
server.enqueue( new MockResponse().setResponseCode(HttpURLConnection.HTTP_OK, Set.of()));
exchange.cancelOrder(orderId);
}
Because the implementation of cancelOrder() calls api.cancelOrder() and then api.getOrders(), I added two mocked responses corresponding to each. However, looks like only the first mocked responses gets returned. For the second (getOrders), the mock server actually tries to connect over REST and then fails by timing out.
Any ideas as to how to mock responses for nested calls?
Cheers!

I ended up using the Dispatcher to check the path of the request.
If the path ends in "get-orders", I send mocked response for Orders otherwise for cancel orders.
Dispatcher dispatcher = (request) -> {
if( request.getPath().endsWith("get-orders"){
return mock response for orders
}else if( request.getPath().endsWith("cxl-orders"){
return mock response for cancel orders
}
}
mockServer.setDispatcher(dispatcher);

Related

Batch operations in JAX-RS

Context
I am currently working on a JavaEE project with a lot of existing resource based JAX-RS services. For this project we would like to have batch processing to prevent a lot of separate calls and, most importantly, to execute these different methods in a transactional context for rollback purposes with the native MongoDB driver. We want to avoid manually creating new methods for all possible combinations. I could not find any solution to this issue on Stack Overflow so I started analyzing the implementation of RESTEasy and I came up with the following solution.
Below a simplified/pseudo version of my code:
JAX-RS method
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#Path("execute")
public Response executeBatch(BatchRequestWrapper batchRequestWrapper) throws UnsupportedEncodingException
{
// Retrieve information from context
HttpServletRequest httpServletRequest = ResteasyProviderFactory.getContextData(HttpServletRequest.class);
HttpServletResponse httpServletResponse = ResteasyProviderFactory.getContextData(HttpServletResponse.class);
ServletContext servletContext = ResteasyProviderFactory.getContextData(ServletContext.class);
HttpResponse httpResponse = ResteasyProviderFactory.getContextData(HttpResponse.class);
SynchronousDispatcher dispatcher = (SynchronousDispatcher) ResteasyProviderFactory.getContextData(Dispatcher.class);
ResteasyHttpHeaders httpHeaders = (ResteasyHttpHeaders) ResteasyProviderFactory.getContextData(HttpHeaders.class);
ResteasyUriInfo uriInfo = (ResteasyUriInfo) ResteasyProviderFactory.getContextData(UriInfo.class);
// Create Mongo Client Session object and save it in a Singleton which contains a ThreadLocal object so that DAO layer can reuse the client session object for all methods.
// Iterate over all the methods and invoke dispatcher
for (BatchRequest batchRequest : batchRequestWrapper.getBatchRequests())
{
// Update URI based on specific endpoint
uriInfo.setRequestUri(URI.create(batchRequest.getUri()));
// Temporary use mock response for the response
MockHttpResponse response = new MockHttpResponse();
// Create httpservletinput message from RESTEasy lib to pass to the dispatcher. It will automatically resolve all parameters/methods etc.
HttpServletInputMessage request = new HttpServletInputMessage(httpServletRequest, httpServletResponse, servletContext, httpResponse, httpHeaders, uriInfo, batchRequest.getHttpMethod(), dispatcher);
// Set body in input stream if body is specified. This will inject the correct 'body' parameters in the methods. Query and Path parameters are already resolved in the method above.
if(!Strings.isNullOrEmpty(batchRequest.getBody()))
{
InputStream targetStream = new ByteArrayInputStream(batchRequest.getBody().getBytes(StandardCharsets.UTF_8));
request.setInputStream(targetStream);
}
// Actual invoke
dispatcher.invoke(request, response);
// Do something with response object
}
// Clean or abort session based on invoke result
return Response.ok().entity(null).build();
}
Request Object
public class BatchRequestWrapper
{
private List<BatchRequest> batchRequests;
public List<BatchRequest> getBatchRequests()
{
return batchRequests;
}
public void setBatchRequests(List<BatchRequest> batchRequests)
{
this.batchRequests = batchRequests;
}
}
public class BatchRequest
{
private String uri;
private String httpMethod;
private String body;
public String getUri()
{
return uri;
}
public void setUri(String uri)
{
this.uri = uri;
}
public String getHttpMethod()
{
return httpMethod;
}
public void setHttpMethod(String httpMethod)
{
this.httpMethod = httpMethod;
}
public String getBody()
{
return body;
}
public void setBody(String body)
{
this.body = body;
}
}
My solution works with one new REST method and let's me reuse all the existing JAX-RS annotated methods in the project. Before I actually fully implement this and bring it to production, I would like to know if this is the way to actually do this or are there better alternatives? I am not a big fan of the hard dependency on RESTEasy though.

Mock jodd http request and response

I'm using Jodd http to make requests. Is there any way to create the mock so the request doesn't happen during unit tests? I've already tried to create a mock of the send() method but without success.
#Service
class ValidateUrlService {
val TIMEOUT = 5000
fun validateUrl(url: String): RequestVO {
var response = HttpResponse()
var timeBefore = Date()
return try{
response = HttpRequest
.post(url)
.timeout(TIMEOUT)
.connectionTimeout(TIMEOUT)
.send()
val httpStatus = response.statusCode()
buildResponseDTO(httpStatusToBoolean(httpStatus), httpStatus)
} catch (ex: Exception) {
genericExceptionHandler(ex, response.statusCode(), timeBefore)
}
}
My test
internal class ValidateUrlServiceTest{
private val service = ValidateUrlService()
#Mock
var request: HttpRequest = HttpRequest.post(ArgumentMatchers.anyString())
#Test
fun test(){
Mockito.`when`(request.send()).thenReturn(HttpResponse().statusCode(555))
service.validateUrl("https://www.example.com")
}
}
Error:
You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
when(mock.get(anyInt())).thenReturn(null);
doThrow(new RuntimeException()).when(mock).someVoidMethod(any());
verify(mock).someMethod(contains("foo"))
Mocking only works with the injected objects. mocking doesn't work for those objects created inside the method scope
You can abstract out the HTTP calls to a different class
class HttpService {
fun post(url: String, timeOut: Long): HttpResponse {
return HttpRequest
.post(url)
.timeout(timeOut)
.connectionTimeout(timeOut)
.send()
}
}
class ValidateUrlService {
val httpService: HttpService
val TIMEOUT = 5000
fun validateUrl(url: String): RequestVO {
var response = HttpResponse()
var timeBefore = Date()
return try{
response = httpService.post(url, TIMEOUT)
val httpStatus = response.statusCode()
buildResponseDTO(httpStatusToBoolean(httpStatus), httpStatus)
} catch (ex: Exception) {
genericExceptionHandler(ex, response.statusCode(), timeBefore)
}
}
Now you should be able to mock the HttpService post method
Your first error is here
#Mock
var request: HttpRequest = HttpRequest.post(ArgumentMatchers.anyString())
Because you are assigning to request a non mock object
If you want to use the annotations you must write you test like this:
(I'm assuming that you are using JUnit5)
#ExtendWith(MockitoExtension::class)
internal class ValidateUrlServiceTest{
private val service = ValidateUrlService()
#Mock
lateinit var request: HttpRequest
#Test
fun test(){
Mockito.`when`(request.send()).thenReturn(HttpResponse().statusCode(555))
service.validateUrl("https://www.example.com")
}
}
The second one is that you are trying to mock an object that is created inside the method you are testing
You must provide an instance of HttpRequest through dependency injection (maybe using a provider injected as a constructor argument to ValidateUrlService)
Small tip:
if you want to avoid the "ugly" syntax Mockito.`when` you can use mokito kotlin library or mockk library, both of them wrap mockito in with a more fluent kotlin way to write tests

How to call rest API in the loop

I want to call the third party API multiple times using the RestTemplate(for each customer id I have to call REST API) currently I have written like below and its working fine but it's taking time because there are many customers I'd and calling API for each customer id, is there any way I can make this parallel.
public List<Organization> getCustomeOrganizationInfo(){
String url="https://url.net/core/v1/customers"
List<Organization> organizationList = new ArrayList<>();
for(Customer customer:CustomerList){
String restUrlWithUserId=url+"/customer.getCustomerId"
CustomerInfo customerInfo = restTemplate.exchange(
restUrlWithUserId,
HttpMethod.GET,
request,
String.class
);
Organization organization =new Organization();
organization.setCustomerId(customer.getCustomerId())
organization.setorganizationId(customerInfo.getCustomeOrganizationId())
organization.setorganizationname(customerInfo.getCustomeOrganizationName())
organizationList.add(organization)
}
}
Is there any way I can make this parallel
For concurrency and clean code, you should separate your restTemplate call to another class(service), for example, ThirdPartyCustomerService.java. This class will be held responsible for calling outside.
#Service
public class ThirdPartyCustomerService {
private final RestTemplate restTemplate;
private final String url = '...';
...
public CustomerInfo getCustomerInfo() {
return this.restTemplate...
}
}
Then you can inject this class into your service class. Now if you want to run it concurrency. You could try #Async and Future here. Just need a little bit of change on the new service and remember to call Future.get() on your main service.
#Async
public Future<CustomerInfo> getCustomerInfo() {
return new AsyncResult<CustomerInfo>(this.restTemplate...);
}
Or you can use WebClient, an alternative for RestTemplate and AsyncRestTemplate.

JUnit/Mockito verifying `any(HttpPut.class)` however passes when other instances used too

I have a service class that calls a REST API to get, create, update and delete subscribers. The Uri remains the same, but the HTTP method changes as you'd expect. I want to test the correct method is given. Below is an example of the updateSubscriber and its test.
public class MyService {
HttpClient httpClient;
public MyService(HttpClient httpClient) {
this.httpClient = httpClient;
}
//...
public int updateSubscriber(Subscriber subscriber) throws ... {
// PUT is the correct method for this request
HttpResponse response = httpClient.execute( new HttpPut( "https://example.org/api/subscribers" ) );
//...
}
//...
Here is my test with JUnit and Mockito:
#RunWith(MockitoJUnitRunner.class)
public class MyServiceTest
{
#Mock
private HttpClient mockHttpClient;
#Mock
private HttpResponse mockResponse;
#Mock
private StatusLine mockStatusline;
#Mock
private HttpEntity mockEntity;
// test subject
private MyService myService;
#Before
public void setup() {
// // this will just ensure http* objects are returning our mocked instances so we can manipulate them..
// when(mockHttpClient.execute(any(HttpGet.class))).thenReturn(mockResponse);
// when(mockHttpClient.execute(any(HttpPost.class))).thenReturn(mockResponse);
// when(mockHttpClient.execute(any(HttpPut.class))).thenReturn(mockResponse);
// when(mockHttpClient.execute(any(HttpDelete.class))).thenReturn(mockResponse);
// when(mockResponse.getStatusLine()).thenReturn(mockStatusline);
// when(mockStatusline.getStatusCode()).thenReturn(HttpStatus.SC_OK);
myService = new MyService(mockHttpClient);
}
#Test
public void testUpdateSubscriber() throws ...
{
when(mockHttpClient.execute(any(HttpPut.class))).thenReturn(mockResponse);
when(mockResponse.getStatusLine()).thenReturn(mockStatusline);
when(mockStatusline.getStatusCode()).thenReturn(HttpStatus.SC_OK);
String responseString = "...";
// this is consumed by a static method which we cannot mock, so we must deal with an actual entity instance
BasicHttpEntity entity = new BasicHttpEntity();
entity.setContent(new ByteArrayInputStream(responseString.getBytes()));
when(mockResponse.getEntity()).thenReturn(entity);
// create a test case Subscriber instance
Subscriber subscriber = new Subscriber();
int statusCode = myService.updateSubscriber(subscriber);
assertEquals(HttpStatus.SC_OK, statusCode);
// just confirm that an HTTP request was made
// TODO this isn't working, still passes when wrong Http* method used
verify(mockHttpClient, times(1)).execute(any(HttpPut.class));
}
//...
However, when I (wrongfully) have the another Http* method instance, it still passes:
// this is wrong, and should fail, but passed :(
HttpResponse response = httpClient.execute( new HttpGet( "https://example.org/api/subscribers" ) );
I'd really like to be able to test this as the action performed could be wrong if the method is mistaken. This test is to ensure that the PUT method was correctly used with the HTTP request for updateSubscriber. Any ideas?
Test passes because HtppPut and HttpGet both are implementation classes of HttpRequestBase, Change the mocking from HttpRequestBase class to HttpPut class
when(mockHttpClient.execute(any(HttpPut.class))).thenReturn(mockResponse);
So now if you try with GET call Test will fail with NullPointerException since GET call has no stub
Not sure if this is the proper answer to my question but I got managed to get the tests to work as intended using a custom argument matcher:
package uk.ac.strath.matchers;
import org.apache.http.client.methods.HttpUriRequest;
import org.mockito.ArgumentMatcher;
public class HttpMethodMatcher implements ArgumentMatcher<HttpUriRequest> {
private String expectedClassName;
// constructors
public HttpMethodMatcher(String expectedClassName) {
this.expectedClassName = expectedClassName;
}
#Override
public boolean matches(HttpUriRequest httpMessage) {
if (httpMessage.getClass().getName().equals(expectedClassName)) {
return true;
}
return false;
}
}
Now in my test, I can do:
verify(mockHttpClient, times(1)).execute( argThat(new HttpMethodMatcher( HttpGet.class.getName() )) );
This tutorial was helpful: https://www.baeldung.com/mockito-argument-matchers

How to TDD for Restful client code example

I did some TDDs before, but they were just straightforward and simple.
However, I will implement a restful client and invoke a restful API of third parties (Twitter, or Jira).
I used Resteasy client framework to implement that. The code is:
public void invokePUT() {
ClientRequest request =
new ClientRequest("http://example.com/customers");
request.accept("application/xml");
ClientResponse<Customer> response = request.put(Customer.class);
try {
if (response.getStatus() != 201)
throw new RuntimeException("Failed!");
} finally {
response.releaseConnection();
}}
If I want to write a test for this method (should write test before implement this method), what kind of the code should I write.
For GET, I can test the return Entity is equals to my expected entity and for POST, I can test the created entity's id is not null.
But how about for PUT and DELETE. Thanks.
Try to use REST Assured testing framework. It is great tool for testing REST services. On their website you'll find tons of examples how to use it. Just use it together with JUnit or TestNG to check assertions and you are done.
Here's how I'd go about the problem in the short term:
1) Extract the request into a parameter to the method. invokePUT() now becomes:
public void invokePUT(ClientRequest request) {
request.accept("application/xml");
ClientResponse<Customer> response = request.put(Customer.class);
try {
if (response.getStatus() != 201)
throw new RuntimeException("Failed!");
} finally {
response.releaseConnection();
}
}
2) In your test, use a stubbed version of ClientRequest
#Test
public void sendsPayloadAsXml() {
StubbedClientRequest request = new StubbedClientRequest(new StubbedResponse());
restApi.invokePUT(request);
assertEquals("application/xml", request.acceptHeader);
}
#Test
public void makesTheCallUsingPut() {
StubbedClientRequest request = new StubbedClientRequest(new StubbedResponse());
restApi.invokePUT(request);
assertTrue(request.putWasCalled);
}
#Test
public void releasesTheConnectionWhenComplete() {
StubbedResponse success = new StubbedResponse();
StubbedClientRequest request = new StubbedClientRequest(success);
restApi.invokePUT(request);
assertTrue(success.connectionWasClosed);
}
#Test(expected = RuntimeException.class)
public void raisesAnExceptionWhenInvalidResponseReceived() {
StubbedClientRequest request = new StubbedClientRequest(new StubbedResponse(400));
restApi.invokePUT(request);
}
private static class StubbedClientRequest extends ClientRequest {
public String acceptHeader = "";
public boolean putWasCalled;
public ClientResponse response
public StubbedRequest(ClientResponse response) {
this.response = response;
}
#Override
public ClientResponse put(Class klass) {
putWasCalled = true;
return response;
}
#Override
public void accept(String header) {
acceptHeader += header;
}
}
private static class StubbedResponse extends ClientResponse {
public boolean connectionWasReleased;
public int status = 201;
public StubbedResponse(int status) {
this.status = status;
}
public StubbedResponse() { }
}
This may not be a perfect design (Handing the ClientRequest to the class and having the RestEasy stuff exposed to the outside world) but it's a start.
Hope that helps!
Brandon
i would inject mocked classes that test, if put and delete was called as intended (with expected parameters and so on). easymock or similar is good for that
(same with post and get)
EDIT:
in case you want to test the rest client, use dependency injection to inject the request, then use easymock to mock it like this (for example to test, if delete is called properly):
#Test void myTest(){
ClientRequest mock = EasyMock.createMock(ClientRequest.class);
mock.delete(2); //test if resource with id=2 is deleted or something similar
EasyMock.replay(mock);
invokeDelete(mock);
EasyMock.verify(mock);
}

Categories