Using Restlet I have created a router for my Java application.
From using curl, I know that each of the different GET, POST & DELETE requests work for each of the URIs and return the correct JSON response.
I'm wanting to set-up JUnit tests for each of the URI's to make the testing process easier. However, I'm not to sure the best way to make the request to each of the URIs in order to get the JSON response which I can then compare to make sure the results are as expected. Any thoughts on how to do this?
You could just use a Restlet Client to make requests, then check each response and its representation.
For example:
Client client = new Client(Protocol.HTTP);
Request request = new Request(Method.GET, resourceRef);
Response response = client.handle(request);
assert response.getStatus().getCode() == 200;
assert response.isEntityAvailable();
assert response.getEntity().getMediaType().equals(MediaType.TEXT_HTML);
// Representation.getText() empties the InputStream, so we need to store the text in a variable
String text = response.getEntity().getText();
assert text.contains("search string");
assert text.contains("another search string");
I'm actually not that familiar with JUnit, assert, or unit testing in general, so I apologize if there's something off with my example. Hopefully it still illustrates a possible approach to testing.
Good luck!
Unit testing a ServerResource
// Code under test
public class MyServerResource extends ServerResource {
#Get
public String getResource() {
// ......
}
}
// Test code
#Autowired
private SpringBeanRouter router;
#Autowired
private MyServerResource myServerResource;
String resourceUri = "/project/1234";
Request request = new Request(Method.GET, resourceUri);
Response response = new Response(request);
router.handle(request, response);
assertEquals(200, response.getStatus().getCode());
assertTrue(response.isEntityAvailable());
assertEquals(MediaType.TEXT_PLAIN, response.getEntity().getMediaType());
String responseString = response.getEntityAsText();
assertNotNull(responseString);
where the router and the resource are #Autowired in my test class. The relevant declarations in the Spring application context looks like
<bean name="router" class="org.restlet.ext.spring.SpringBeanRouter" />
<bean id="myApplication" class="com.example.MyApplication">
<property name="root" ref="router" />
</bean>
<bean name="/project/{project_id}"
class="com.example.MyServerResource" scope="prototype" autowire="byName" />
And the myApplication is similar to
public class MyApplication extends Application {
}
I got the answer for challenge response settings in REST junit test case
#Test
public void test() {
String url ="http://localhost:8190/project/user/status";
Client client = new Client(Protocol.HTTP);
ChallengeResponse challengeResponse = new ChallengeResponse(ChallengeScheme.HTTP_BASIC,"user", "f399b0a660f684b2c5a6b4c054f22d89");
Request request = new Request(Method.GET, url);
request.setChallengeResponse(challengeResponse);
Response response = client.handle(request);
System.out.println("request"+response.getStatus().getCode());
System.out.println("request test::"+response.getEntityAsText());
}
Based on the answer of Avi Flax i rewrite this code to java and run it with junitparams, a library that allows pass parametrized tests. The code looks like:
#RunWith(JUnitParamsRunner.class)
public class RestServicesAreUpTest {
#Test
#Parameters({
"http://url:port/path/api/rest/1, 200, true",
"http://url:port/path/api/rest/2, 200, true", })
public void restServicesAreUp(String uri, int responseCode,
boolean responseAvaliable) {
Client client = new Client(Protocol.HTTP);
Request request = new Request(Method.GET, uri);
Response response = client.handle(request);
assertEquals(responseCode, response.getStatus().getCode());
assertEquals(responseAvaliable, response.isEntityAvailable());
assertEquals(MediaType.APPLICATION_JSON, response.getEntity()
.getMediaType());
}
}
Related
I have been trying to use restful webservice in place of legacy soap webservice without altering the user part of the request. I wanted to know whether this is achievable. Here is a sample code to demonstrate the issue :
Soap request :
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:sch="https://www.flopradalley.com/xml/school">
<soapenv:Header/>
<soapenv:Body>
<sch:StudentDetailsRequest>
<sch:name>Arun</sch:name>
</sch:StudentDetailsRequest>
</soapenv:Body>
</soapenv:Envelope>
And this is my rest controller which should be able to respond to the request from SoapUI :
#RestController
#RequestMapping(value = "/restservice")
public class KenRestController {
#RequestMapping(value = "/details", method = RequestMethod.POST,
produces = MediaType.TEXT_XML_VALUE, consumes = MediaType.TEXT_XML_VALUE
)
public StudentDetailsResponse execute(HttpServletRequest request)
{
StudentDetailsRequest objectFromBody = null;// unmarshall the object from soap request and store it here
StringBuffer sb = new StringBuffer();
try {
BufferedReader reader = request.getReader();
String lineStr = null;
while ((lineStr = reader.readLine()) != null) {
sb.append(lineStr);
}catch(IOException ex) {
ex.printStackTrace();
}
/* other code */
return ---;
}
}
I was only able to get the soap request as text and store it in a string buffer. I know this isn't remotely the way it should be done if it is possible to handle soap requests using rest. I know that rest is an archicture and is a whole lot difference from soap and it looks like there is no direct way to handle soap requests using rest at the least.
Apart from this sample code, I should be able to extract the SoapMessage, MessageContext, Soapheader, SoapBody from the request. That brings me to the original question on whether it is possible?
If you make the change into a REST API I highly recommend to not keep thinging in some of the webservice logic.
The SOAP protocol is a method to pass information that contains both the answer and the metadata of the call.
In rest you obtain the answer in the body of the responses and the most used format is json. You can directly pass as a parameter in the method the object that is going to be parsed into the java object by Spring REST (it uses Jackson library to parse in reality).
#RestController
public class KenRestController {
#GetMapping("/restService/details/{studentId}") // post is for saving an elelement
public StudentDetailsResponse getDetails(#PathVariable("studentId") Integer studentId) {
// you get the details and parse it to the object you want to return
return studentService.getStudentById(studentId);
}
// example of response class
private class StudentDetailsResponse implements Serializable{
// pojo class
}
}
Please don't hesitate to make any questions about that.
From my backend application(springboot, java8) i will make multiple external api call's. I have a requirement to log all the requests and response data's (including headers, request and response body) into database(MongoDB).
Below is my sample code, this how i am trying to capture request and responses on each external api calls. On exception i will store status as 'FAILED'.
In my project multiple modules will be added on new 3rd party api integration, so in each module for every different external api calls i have to capture all the requests and reponses like this. I am not satisfied with below approach. Kindly suggest best approach to solve this.
Sample Service layer method
public ResponseDTOFromExternalApi externalApiCallServiceMethod(String clientId, RequestDTO requestDTO) {
ExternalApiCallRequestObj externalApiCallRequestObj = prepareExternalApiRequestObjFromRequestDTO(requestDTO);
ApiCall apiCall = ApiCall.builder()
.clientId(clientId)
.status("SUBMITTED")
.requestPayload(externalApiCallRequestObj)
.build();
apiCall = apiCallRepository.save(apiCall);
ExternalApiCallReponseObj externalApiCallReponseObj = externalApiCallService.callExternalApi1(externalApiCallRequestObj);
apiCall = apiCallRepository.findById(apiCall.getId());
apiCall.setResponsePayload(externalApiCallReponseObj);
apiCall.setStatus("COMPLETED");
apiCallRepository.save(apiCall);
return toDTO(externalApiCallReponseObj);
}
Sample Domain for api calls
#Document("api_calls")
#Builder
#Data
public class ApiCall {
#Id
private String id;
private String clientId;
private String status;
private Object requestPayload;
private Object responsePayload;
}
Spring's WebClient already has the ability to log all request and response data by adding exchange filters.
By using it for your network requests the only thing left to do is to write this information in your mongodb.
Here is tutorial on logging requests and responses:
https://www.baeldung.com/spring-log-webclient-calls
Cheers
You may use Spring AOP to address this cross cutting concern.
Assuming ExternalApiCallService is a spring managed bean , following code will intercept all the callExternalApi1() and can log the same to database.
#Component
#Aspect
public class ExternalCallLoggerAspect {
#Autowired
ApiCallRepository apiCallRepository;
#Pointcut("execution(* *..ExternalApiCallService.callExternalApi1(..))")
public void externalApiCallService(){}
#Around("externalApiCallService() && args(request)")
public ExternalApiCallReponseObj logCalls(ProceedingJoinPoint pjp,ExternalApiCallRequestObj request){
Object result=null;
String status = "COMPLETED";
ExternalApiCallReponseObj response = null;
// Build the apiCall from request
ApiCall apiCall = ApiCall.builder()
.clientId(clientId)
.status("SUBMITTED")
.requestPayload(request)
.build();
//save the same to db
apiCall = apiCallRepository.save(apiCall);
// Proceed to call the external Api and get the result
try {
result = pjp.proceed();
} catch (Throwable e) {
status = "FAILED";
}
//Update the response
apiCall = apiCallRepository.findById(apiCall.getId());
apiCall.setStatus(status);
apiCallRepository.save(apiCall);
if(result != null) {
response = (ExternalApiCallReponseObj)result;
apiCall.setResponsePayload(response);
}
//continue with response
return response;
}
}
Note
1.There is a typo with the name ExternalApiCallReponseObj
2.The aspect code is verified that it works and the logic was included later on untested. Please make the required corrections
Ideally the original method should be stripped down to this
public ResponseDTOFromExternalApi externalApiCallServiceMethod(String clientId, RequestDTO requestDTO) {
return toDTO(externalApiCallService.callExternalApi1(prepareExternalApiRequestObjFromRequestDTO(requestDTO)));
}
More about Spring AOP here
Update : on a second thought , if all the external api calls are through a single method , say ExternalApiCallService.callExternalApi1() , this logging logic can be moved to that common point , isn't it ?
Problem
How to forward requests in Spring Cloud application? I need to forward requests to other services depending on the part of uri.
For example
HTTP GET http://user-application/api/users, returns users JSON.
HTTP GET http://user-application/api/proxy/jobs-application/api/jobs, returns jobs JSON, but this request should be forwarded to another application:
HTTP GET http://jobs-application/api/jobs.
Any HTTP method is allowed, not only GET.
Context
I have a SpringBoot Application, User application which has REST end-points which return data.
For example GET http://user-application/api/users would return users in the JSON format.
User application also has an HTTP end-point which should forward the request to other applications - let's call one of them Jobs application.
This end-point is HTTP {ANY_METHOD} /api/proxy/{dynamic-service}/{dynamic-path} as an example,
GET http://user-application/api/proxy/jobs-application/api/jobs
Please, note, initial request comes to the User application, while then it is forwarded to the Jobs application.
Approaches
I put some my approaches which I think about. Maybe you have done similar things in the past, so you could share your experience doing so. Or even improve one of my approaches.
ProxyController approach
I would create a ProxyController in User application with mapping /proxy
#Controller
#RequestMaping("/proxy/**")
ProxyController
public void proxy(final HttpServletRequest request, HttpResponse response) {
final String requestUri = request.getRequestUri();
if (!requestUri.startsWith("/api/proxy/")) {
return null; // Do not proxy
}
final int proxyIndex = "/api/proxy/".lenght(); // Can be made a constant
final String proxiedUrl = requestUri.subString(proxyIndex, requestUri.lenght());
final Optional<String> payload = retrievePayload(request);
final Headers headers = retrieveHeaders(request);
final HttpRequest proxyRequest = buildProxyRequest(request, headers);
payload.ifPresent(proxyRequest::setPayload);
final HttpResponse proxyResponse = httpClient.execute(proxyRequest)
pdateResponse(response, proxyResponse);
}
The problem with this approach, I have to write a lot of code t build a proxy request, to check if it has payload and if it has, copy it into proxy request, then copy headers, cookies etc to the proxy request, copy HTTP verb into proxy request. Then when I get proxy response, I have to populate its details into the response.
Zuul approach
I was inspired by ZuulFilters:
https://www.baeldung.com/spring-rest-with-zuul-proxy
https://stackoverflow.com/a/47856576/4587961
#Component
public class ProxyFilter extends ZuulFilter {
private static final String PROXY_PART = "/api/proxy";
private static final int PART_LENGTH = PROXY_PART.length();
#Autowired
public ProxyFilter() {
}
#Override
public boolean shouldFilter() {
final RequestContext context = RequestContext.getCurrentContext();
final String requestURI = retrieveRequestUri(context);
return requestURI.startsWith(PROXY_PART);
}
#Override
public Object run() {
final RequestContext context = RequestContext.getCurrentContext();
final String requestURI = retrieveRequestUri(context);
final String forwardUri = requestURI.substring(PART_LENGTH);
context.setRouteHost(buildUrl(forwardUri));
return null;
}
#Override
public String filterType() {
return "proxy";
}
#Override
public int filterOrder() {
return 0;
}
private String retrieveRequestUri(final RequestContext context) {
final HttpServletRequest request = context.getRequest();
return request.getRequestURI();
}
private URL buildUrl(final String uri) {
try {
return new URL(uri);
} catch (MalformedURLException e) {
throw new RuntimeException(String.format("Failed to forward request uri %s}.", uri), e);
}
}
}
This code allows me to forward requests with less effort. However, we also use client side load balancer Ribbon and circuit breaker Hystrix in Spring Cloud Zuul out of box. How to enable these features? Will they be enabled out of box in context.setRouteHost(forwardUrl);
I would like to add another approach, maybe it can also work.
Static application.yml file to configure Zuul proxy approach
This approach does not requre dynamic Zuul Filters.
application.yml
zuul:
routes:
user-application:
path: /api/users/**
serviceId: user-service
stripPrefix: false
sensitiveHeaders:
# I have to define all other services similarly.
jobs-application:
path: /api/proxy/jobs/**
serviceId: jobs-application
stripPrefix: true
sensitiveHeaders:
It will work only if I know all the services my clients need to call before I deploy the User application. What if a new application is added dynamically? Then I will have to update the configuration.
I have the following signature for my Micronaut file upload controller (in Java):
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces("application/json")
#Post
public Single<IdType> uploadFile(Publisher<CompletedFileUpload> files)
And I have the following working Spock test (in Groovy):
#MicronautTest
class UploadSpecification extends Specification implements CsvFileBuilder {
#Inject
#Client('/')
HttpClient client
#Shared
List<String> allowedMimeTypes = List.of("text/csv", "application/vnd.ms-excel")
#Unroll
void "upload mailings csv with content type #mediaType"() {
given:
MultipartBody multipartBody = MultipartBody
.builder()
.addPart("files", "myfile.csv", new MediaType(mediaType), createCsvAsBytes(buildCsv()))
.build()
when:
HttpResponse response = client.toBlocking()
.exchange(POST("/v1/mailings", multipartBody).contentType(MediaType.MULTIPART_FORM_DATA_TYPE))
then:
response.status == HttpStatus.OK
where:
mediaType << allowedMimeTypes
}
}
What I would like to change about the test is: Instead of using the standard HttpClient as injected at the top of the test, I would like to use something like this:
#Inject
UploadClient uploadClient
#Client(value = "/v1/mailings")
static interface UploadClient {
#Post
HttpResponse postFile(...)
}
My question is, what signature, does the postFile of the client need? Will I still be able to use MultipartBody but somehow convert it to a CompletedFileUpload? I'm really not sure how to solve this and I'm a beginner when it comes to RxJava.
Any help is appreciated.
Please try following:
#Client(value ="/v1/upload")
static interface UploadClient {
#Post(uri = "/mailings", produces = MediaType.MULTIPART_FORM_DATA)
HttpResponse postFile(#Body MultipartBody file)
}
Add produces and the body annotation
I am writing a Java class that uses Jersey under the hood to send an HTTP request to a RESTful API (3rd party).
I would also like to write a JUnit test that mocks the API sending back HTTP 500 responses. Being new to Jersey, it is tough for me to see what I have to do to mock these HTTP 500 responses.
So far here is my best attempt:
// The main class-under-test
public class MyJerseyAdaptor {
public void send() {
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
String uri = UriBuilder.fromUri("http://example.com/whatever").build();
WebResource service = client.resource(uri);
// I *believe* this is where Jersey actually makes the API call...
service.path("rest").path("somePath")
.accept(MediaType.TEXT_HTML).get(String.class);
}
}
#Test
public void sendThrowsOnHttp500() {
// GIVEN
MyJerseyAdaptor adaptor = new MyJerseyAdaptor();
// WHEN
try {
adaptor.send();
// THEN - we should never get here since we have mocked the server to
// return an HTTP 500
org.junit.Assert.fail();
}
catch(RuntimeException rte) {
;
}
}
I am familiar with Mockito but have no preference in mocking library. Basically if someone could just tell me which classes/methods need to be mocked to throw a HTTP 500 response I can figure out how to actually implement the mocks.
Try this:
WebResource service = client.resource(uri);
WebResource serviceSpy = Mockito.spy(service);
Mockito.doThrow(new RuntimeException("500!")).when(serviceSpy).get(Mockito.any(String.class));
serviceSpy.path("rest").path("somePath")
.accept(MediaType.TEXT_HTML).get(String.class);
I don't know jersey, but from my understanding, I think the actual call is done when get() method is invoked.
So you can just use a real WebResource object and replace the behavior of the get(String) method to throw the exception instead of actually execute the http call.
I'm writing a Jersey web application... and we throw WebApplicationException for HTTP error responses. You can simply pass the response code as the constructor-parameter. For example,
throw new WebApplicationException(500);
When this exception is thrown server-side, it shows up in my browser as a 500 HTTP response.
Not sure if this is what you want... but I thought the input might help! Best of luck.
I was able to simulate a 500 response with the following code:
#RunWith(MockitoJUnitRunner.class)
public class JerseyTest {
#Mock
private Client client;
#Mock
private WebResource resource;
#Mock
private WebResource.Builder resourceBuilder;
#InjectMocks
private Service service;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void jerseyWith500() throws Exception {
// Mock the client to return expected resource
when(client.resource(anyString())).thenReturn(resource);
// Mock the builder
when(resource.accept(MediaType.APPLICATION_JSON)).thenReturn(resourceBuilder);
// Mock the response object to throw an error that simulates a 500 response
ClientResponse c = new ClientResponse(500, null, null, null);
// The buffered response needs to be false or else we get an NPE
// when it tries to read the null entity above.
UniformInterfaceException uie = new UniformInterfaceException(c, false);
when(resourceBuilder.get(String.class)).thenThrow(uie);
try {
service.get("/my/test/path");
} catch (Exception e) {
// Your assert logic for what should happen here.
}
}
}