I've made a basic test for servlet to test it's response status code, but it doesn't work - it's always 0, although I've set the response status code inside the servlet to 200.
public class TestMyServlet extends Mockito {
#Test
public void test() throws Exception {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
when(request.getParameter("test")).thenReturn("1");
new MyServlet().doPost(request, response);
System.out.println(response.isCommited()); // false
System.out.println(response.getContentType()); // null
System.out.println(response.getStatus()); // 0
}
}
How to let this work?
You want to test this differently. You need to verify that your inputs caused the expected outputs. For non-mock results, you would assert the behavior. Since you want to verify that your outputs were set properly.
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class MyServletTests {
#Test
public void testValidRequest() throws Exception {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
when(request.getParameter("test")).thenReturn("1");
new MyServlet().doPost(request, response);
// ensure that the request was used as expected
verify(request).getParameter("test");
// ensure that the response was setup as expected based on the
// mocked inputs
verify(response).setContentType("text/html");
verify(response).setStatus(200);
}
}
If you expect something to not be touched given certain inputs, then you should consider verifying that behavior using verify(response, never()).shouldNotBeCalledButSometimesIs() (to verify when conditions control it being called/set versus not).
You're mocking HttpServletResponse. So, since it's a mock, getStatus() will only return a non-zero value until you tell the mock to return something else when getStatus() is called. It won't return the value passed to setStatus(), which, since it's a mock, doesn't do anything.
You could use a "smarter" mock HttpServletResponse, like the one provided by Spring.
Related
I consider myself a novice at unit-testing, completely new to Mockito and junit. I have to write unit-tests for some simple api-calls. But my test seems somewhat pointless to me, I can't tell where I am going wrong. I have added a method to an existing web-service, ManagerWS.java , See Below.
ManagerWS.java Method:
public String healthCheck(String userId) {
String healthCheckUrlEndpoint = this.baseUrl()+"/health";
logger.debug("Calling health check: {}", healthCheckUrlEndpoint);
HttpHeaders healthCheckHeaders = new HttpHeaders();
healthCheckHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
healthCheckHeaders.add(USER_KEY, userId);
healthCheckHeaders.add(TOKEN_NAME, TOKEN_VALUE);
healthCheckHeaders.add(Constants.ACCEPT_LANGUAGE_HEADER, LocaleContextHolder.getLocale().toString());
healthCheckHeaders.add(CORRELATION_HEADER, myService.get(AppLoggingMDCService.LOG_KEY_REQUEST_ID));
HttpEntity<Object> request = new HttpEntity<Object>(healthCheckHeaders);
ResponseEntity<String> response;
try {
response = makeRequest(HttpMethod.GET, healthCheckUrlEndpoint, request, String.class);
} catch (Exception e) {
logger.error("Exception encountered during health check", e);
throw e;
}
logger.debug("RESPONSE : http status: {} - body: {}", response.getStatusCode(), response.getBody());
return response.getStatusCode().toString();
}
The logic is simple. Construct the url, create headers and add headers to the request. make the request and extract the status-code from the response. Here is my test. NOTE: the class is using #RunWith(SpringRunner.class) and I am using #Mock for dependencies and #InjectMocks for the local instance ManagerWS. ManagerWS.java is the service calss being tested.
TEST-CLASS TEST-Method:
#Test
public void testHealthCheck() throws Exception {
//Given
managerWS = new ManagerWS(templateFactory, configParamService, mdcService, env);
String url = "http://baseurl/health";
HttpHeaders headers = new HttpHeaders();
HttpEntity<Object> request = new HttpEntity<Object>(headers);
ResponseEntity<String> response = new ResponseEntity<>(HttpStatus.OK);
//when
when(managerWS.makeRequest(HttpMethod.GET, url, request, String.class)).thenReturn(response);
String actualStatus = response.getStatusCode().toString();
//then
Assert.assertEquals("200",actualStatus);
}
To me this test seems stupid (for want of a batter word). I basicall set the status to give a "200" and assert that what i set is "200". That is not really making much sense.To me it literally does nothing. I tried using spy(ManagerWS.class). But I am literally grasping at straws without the full understanding.
SonarQube still complains with "Not covered by unit tests". I cam completely stumped as to how else to write this test. I also have to do similar tests for three other calls.
I am a total novice to testing and I cannot see my mistake. Please advise.
SonarQube still complains with "Not covered by unit tests".
Your unit test doesn't test from the entry point of the method to test : healthCheck(String), so it is not covered by unit tests.
Besides, you also mock the part of the method that you want to test :
when(managerWS.makeRequest(HttpMethod.GET, url, request, String.class)).thenReturn(response);
So indeed your approach looks wrong.
In fact, writing an unit test for this code looks wrong too or at least looks like a white box test with few value.
Why ?
Your logic depends on :
response = makeRequest(HttpMethod.GET, healthCheckUrlEndpoint, request, String.class);
But you can know if it works only at runtime, with a running HTTP Server.
So the single thing that you can do is mocking everything, spying the object under test and verifying that each statement in the implementation is performed : no readable test, no robust and few/no value.
Your method that relies essentially on side effect would make more sense to be tested with as an integration test :
ManagerWS managerWS; // real ManagerWS implementation without mock
#Test
public void healthCheck() throws Exception {
//Given
String url = "http://baseurl/health";
// When
String actual managerWS.healthCheck(url);
// Then
String expected = "...";
Assertions.assertEquals(expected, actual);
}
As a side note, if you used Spring, I would advise you to look at test slicing #WebMvcTest that focuses on the web part of the component under test. It allows mainly to test the HTTP part/logic (headers, request, response).
As stated in title, what does it mean that HttpServletResponse is committed?
I have some request interceptor, extending HandlerInterceptorAdapter, that overrides postHandle method. Post handle method takes parameter final HttpServletResponse response. In method body there is an if statement checking if response.isCommitted(), what exactly does that check?
private static final String voidResponse = "null";
#Override
public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler,
final ModelAndView modelAndView) throws IOException {
if (!response.isCommitted()) {
if (DefaultServletHttpRequestHandler.class == handler.getClass()) {
return;
}
response.setStatus(200);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
try (final Writer writer = response.getWriter()) {
writer.write(voidResponse);
}
log.info("void method called, respond with 200 and null");
response.flushBuffer();
}
}
ServlerResponse.isCommited() checks if the response has been already committed to the client or not (Means the servlet output stream has been opened to writing the response content).
The committed response holds the HTTP Status and Headers and you can't modify it.
It's important also to note that in this case the response content has NOT been written yet, as the headers and status are committed before the content itself.
In such examples as yours the check is required to prevent situations when the response has already been commited but someone is trying to modify it, in which case you will get an IllegalStateException stating that response has already been committed.
UPDATE: I see that you are using Spring controllers. The story differs a bit here.
Case 1: If you are using #ResponseBody in your controller method or returning ResponseEntity Spring writes to and commits the response before the postHandle() is called, which makes it impossible to change the response later. That said in this case response.isCommited() statement will always return true and you are not able to modify the response.
Case 2: If you don't have the above mentioned annotation and don't return ResponseEntity or controller returns NULL the postHandle() method of interceptor is called after the controller method has been processed, but the response has not been committed yet. This means you can modify the response as you want (e.g. return 200 OK).
It is response committed not request. It means response already sent to output stream/client.
I have a controller mapping, where I pass 2 request params instead of 1. But when done like that Spring is not throwing any exception rather it is ignoring the additional request params.
For eg:
#RequestMapping(value="/test", method = RequestMethod.GET)
public ModelAndView eGiftActivation(#RequestParam("value") String value)
When I hit my app using /test.do?value=abcd it is working fine. But when I pass additional params like /test.do?value=abcd&extra=unwanted also it's working fine.
In this case I want Spring to restrict the second URL where additional params are passed.
How can I achieve this?
You could check it manually, like this:
#RequestMapping("/test")
public ModelAndView eGiftActivation(HttpServletRequest request) {
Map<String, String[]> params = request.getParameterMap();
if (params.size() != 1 || !params.containsKey("value")) {
throw new RuntimeException("Extra parameters are present"); // or do redirect
}
...
}
I don't think it's possible (For Spring to prevent the request to flow to any controller's method). The reason being that:
Your controller handles request based on the URI path like, /app/hello/{name} rather than the request parameters
Request parameters are there to give extra set of meta-info for the request rather than endpoint specification of request.
But, if you wanted to restrict the URI path as such, you can use regex and you can avoid. I'm afraid it's not feasible and even the requirement for that never arose.
Programmatical Way:
Having said that, you can take HttpServletRequest for parameters and loop through the parameters to check for extra ones:
#RequestMapping(value="/test", method = RequestMethod.GET)
public Object eGiftActivation(#RequestParam("value") String value, HttpServletRequest request){
//check the request.getParameterMap() and throw custom exception if you need and handle using Exception handler or throw invalid request
return new ResponseEntity<String>(HttpStatus.SC_BAD_REQUEST);
}
I prefer handling these kind of validations (if required, what ever may be the reason) inside the Filter generically so that the requests will not even reach the Controller methods.
Please find the required code to handle inside the Filter as below (logic is almost similar to Slava).
#Component
public class InvalidParamsRequestFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Map<String, String[]> params = request.getParameterMap();
if (request.getRequestURI().contains("/test") && (params.size() != 1 || !params.containsKey("value"))) {
//Here, Send back the Error Response OR Redirect to Error Page
} else {
filterChain.doFilter(request, response);
}
}
}
I'm currently trying to get a unit test set up for an HttpServlet class I have in Java. However, the Jetty documentation is kind of lacking and I'm a little stuck. I'm fairly certain the contextPath is /hbc as I printed it out using getContextPath() in the servlet. However, I'm not certain what a) the second parameter to the addServlet() method should be and b) what the URI should be.
The status code keeps returning back as 400 and the content is null. I'm not sure if it's because i'm not pointing to the right location (but I would think that would lead to a 404) or if something else is missing.
The servlet has an init(), processRequest(), doGet(), and doPost() method.
Thoughts?
public class HBCUnitTests extends TestCase {
private ServletTester tester;
#BeforeClass
public void setUp() throws Exception {
tester = new ServletTester();
tester.setContextPath("/hbc");
tester.addServlet(HubCommServlet.class, "/");
tester.start();
}
#AfterClass
public void tearDown() throws Exception {
tester.stop();
}
#Test
public void test() throws Exception {
HttpTester request = new HttpTester();
request.setMethod("POST");
request.setVersion("HTTP/1.1");
request.setURI("/");
System.out.println(request.generate());
HttpTester response = new HttpTester();
response.parse(tester.getResponses(request.generate()));
System.out.println(response.getContent());
System.out.println(response.getURI());
System.out.println(response.getReason());
assertEquals(200,response.getStatus());
assertEquals("<h1>Hello Servlet</h1>",response.getContent());
}
}
It looks like the second argument to addServlet() is the servlet-mapping.
If the contextPath is /hbc and your servlet is mapped to / then I would expect that you need to request /hbc/:
HttpTester request = new HttpTester();
...
request.setURI("/hbc/");
I ended up using JMock to test the servlet.
Forgive me, but I may not be familiar with all the lingo necessary to ask this question properly.
I'm working on a fairly simple REST web service in Java using the org.apache.cxf.jaxrs.ext implementation of jax-rs. The method header is like this:
#GET
#Path("json/{fullAlias}")
#Produces({"application/json"})
public String json(#PathParam("fullAlias") String fullAlias, #Context MessageContext req)
where MessageContext is org.apache.cxf.jaxrs.ext.MessageContext.
There are two things I'm trying to accomplish that I can't seem to figure out:
Change the content-type if certain conditions are met (e.g. for an error)
Change the status code of the response
I've tried using changing the response by accessing it through the MessageContext:
HttpServletResponse response = req.getHttpServletResponse();
response.setContentType("text/plain")
response.setStatus("HttpServletResponse.SC_BAD_REQUEST);
But these changes have no bearing on the response sent; with or without the #Produces annotation, setting the content type inside the method doesn't affect the actual content type (With the annotation, it of course returns "application/json", without it defaults to "text/html").
I am returning a simple String as the body. I've entertained trying to return a javax.ws.rs.core.Response object to do what I want, but I don't know much about it.
How would I change the content type and/or the status codes from inside this method?
One approach is to throw a WebApplicationException, as described by Pace, which will work if you are looking to specifically handle an error condition. If you are looking to be able to change your content at any time for any reason, then you will want to take a look at returning a Response as the result of your service method rather than a String. Returning a Response gives you the greatest amount of control over how your service responds to the client request (it does require more code than returning a simple string).
Here is an example of how you would can make use of the Response object:
#GET
#Path("json/{fullAlias}")
public Response json(#PathParam("fullAlias") String fullAlias, #Context MessageContext req) {
...
if (success) {
ResponseBuilder rBuild = Response.ok(responseData, MediaType.APPLICATION_JSON);
return rBuild.build();
}
else {
ResponseBuilder rBuild = Response.status(Response.Status.BAD_REQUEST);
return rBuild.type(MediaType.TEXT_PLAIN)
.entity("error message")
.build();
}
}
I'm not sure if it's the best approach but I've done the following to solve your question #1.
public WebApplicationException createStatusException(String statusMessage) {
ResponseBuilder rb = Response.noContent();
rb = rb.type(MediaType.TEXT_PLAIN);
rb = rb.status(Status.BAD_REQUEST);
rb = rb.entity(statusMessage);
return new WebApplicationException(rb.build());
}
EDIT: I then threw the resulting WebApplicationException.
You can write your own Response Filter to change the content-type header.
#Provider
public class MimeAddingFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("Content-Type", "image/png");
}
}
This filter will add the "image/png" content-type header. You can also change or remove headers in JAX-RS response filters.