ive tried switch to a when() and get compiler errors
i have successfully used doThrow(...) in another test in the same project, so i dont know whats going on here
Unit test code:
doThrow(new Exception("the client cancelled the request, ya dingus!")).when(handler).write(any());
MvcResult result = mockMvc.perform(post("/myPath")
.content(String.valueOf(mockValidRequest))
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("utf-8"))
.andExpect(status().is5xxServerError())
.andReturn();
Code I am testing (handler method for /myPath):
#PostMapping("/myPath")
public ResponseEntity<String> handleRequest(#RequestBody MyPojo request) {
try {
handler.write(request);
} catch (Exception e) {
return new ResponseEntity<>("Exception thrown during event processing: " + e, HttpStatus.SERVICE_UNAVAILABLE);
}
return new ResponseEntity<>("Success", HttpStatus.OK)
}
The problem is the test says the actual result is a 200 success, when there should have been an exception caught, and a service unavailable 5xx response.
So I found the proper answer to my issue:
I didnt have this snippet in my original post, but the handler was being instantiated in the test like this:
#Mock
EventHandler handler;
it needed to be:
#MockBean
EventHandler handler;
My guess is since #MockBean is the spring mock, and #Mock is from Mockito, it was probably mocking an event handler that was instantiated outside of the Spring container. Thus why I didnt think it was picking up the doThrow ... it did pick it up, but spied on the wrong instance.
i found that i could get the tests to pass calling the function directly ... it just seems like MockMvc is not incorporating the doThrow logic
revised unit test:
doThrow(new Exception("some error")).when(handler).sendToSqsWriter(any());
ResponseEntity<String> response = controller.handleRequest(new Gson().fromJson(new JSONObject(mockApptRequestBody);.toString(), SeamAppointmentRequest.class));
assertTrue(response.toString().contains("some error"));
assertTrue(response.getStatusCodeValue() == 503);
all the json/gson jazz is resolving runtime parsing errors
Related
I have this method in my Java application:
#Override
public List<Client> getClients(String username) {
ParameterizedTypeReference<List<Client>> tRef = new ParameterizedTypeReference<List<Client>>() {
};
HttpHeaders headers = createHeaders();
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
HttpEntity<String> request = new HttpEntity<>(headers);
ResponseEntity<List<Client>> response = restTemplate.exchange(aPIEndpoint + Constants.GET_CLIENTS,
HttpMethod.GET, request, tRef, username);
if (response.getStatusCode().is2xxSuccessful()) {
return response.getBody();
} else {
return new ArrayList<>();
}
}
I noticed in one of my unit tests that line if (response.getStatusCode().is2xxSuccessful()) { causes a NullPointerException. I'm wondering how I should deal with this. Should I handle this exception in my getClients() method or does my test need fixed in some way?
Just fixing your unit test won’t help handling real error cases.
To me, your method would definitely benefit from a try / catch as it performs an external call which you can’t predict.
Then, you could mock your API call and simulate various scenarios to make sure you handle all cases (success or failure) with unit tests.
You get NPE cause restTemplate.exchange(..) returns Null response.
RestTemplate can be mock object and you can return mock response.
using RestTemplate as mock. https://www.baeldung.com/spring-mock-rest-template
or use TestRestTempate. https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/web/client/TestRestTemplate.html
I'm new to TestRestTemplate and with in Spring framework in general, and I'm trying to verify if a ResponseStatusException is thrown by my controller. For example the following degenerated request:
#RestController
public class UserManagementController {
#RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public ResponseEntity<UserDTO> updateUser(#RequestBody UserDTO userDTO, #PathVariable("id") String id){
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "user not found");
}
}
And in my test I'm using TestRestTemplate:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserManagementComponentTest {
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void testUpdateStaleUser() {
UserDTO updateUserDTORequest = UserDTO.builder();
assertThrows(ResponseStatusException.class,
() -> testRestTemplate.exchange("/users/" + createdUserId,
HttpMethod.PUT, new HttpEntity<>(updateUserDTORequest), UserDTO.class));
}
}
I expect to get ResponseStatusException, but the tests fails with the following message:
org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> expected: <org.springframework.web.server.ResponseStatusException> but was: <org.springframework.web.client.RestClientException>
I don't understand why RestClientException is thrown.
ResponseStatusException is the exception thrown in the server side and it will be handled by the spring-mvc framework in the server internally to return the suitable HTTP error response.
While TestRestTemplate just like a client-side REST library and hence it never can catch and handle the exception that is thrown internally from the API server.
TestRestTemplate can only throw its own exception when handling the HTTP response returned from calling an API. It will delegate to its internal RestTemplate 's ResponseErrorHandler for handling the error HTTP response.
So RestClientException is thrown by the ResponseErrorHandler that you configured for the TestRestTemplate.
Actually by default , the TestRestTemplate is configured to be fault tolerant such that it behaves in a test-friendly way by not throwing exceptions such that you can asserting directly on the returned HTTP status code or payload (see this) :
ResponseEntity<String> response = testRestTemplate.exchange("/users/" + createdUserId, HttpMethod.PUT, new HttpEntity<>(updateUserDTORequest), UserDTO.class));
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(response.getBody()).isEqualTo(xxxxx);
I am new to JUnit and Mockito framework. Here I am trying to mock the rest template and wanted to return an HTTP Status of 200, but still, it returns a null value. Can someone tell me whats is wrong with my implementation and why is it returning a null value, not HttpStatus OK .
Class MyDependencies{
#Autowired
RestTemplate template;
}
Class ABC extends MyDependencies{
void verify(){
try{
ResponseEntity<Object> response;
try{
response= template.postForEntity("localhost:....",obj,obj);
}catch(Exception e){
throws Exception.......
}
if(response.getStatusCodeValue()==200) // When reaches here, Exception is thrown
// becoz response is null
return new ResponseEntity<>(HttpStatus.OK);
else
throw Custom_Exception.....
}catch(Exception e){
throws Exception.....
}
}
}
Testing
Class MyTesting{
#InjectMocks
ABC abc;
#Mock
RestTemplate template;
#BeforeEach
void setUp(){
abc=new ABC();
MockitoAnnotations.initMocks(this);
}
#Test
void testingIt(){
when(template.postForEntity(anyString(),any(),any())).thenReturn(new ResponseEntity<>(HttpStatus.OK));
Assertions.asserDoesNotThrow(()->abc.verify());
}
}
The issue is the matchers which we are supplying from test method are not matching the actual function call in the service class, hence the stub is not executed and the response is null.
Try specifying the when stub like this
when(restTemplate.postForEntity(anyString(), any(), Mockito.<Class<String>>any()))
.thenReturn(new ResponseEntity<String>("{\"status\" : \"ok\"}", HttpStatus.OK));
In this case, I have taken the String as an entity I am posting through rest template. You may use some other class here.
Service class call for this will look like this
ResponseEntity<String> answer = template.postForEntity("/abc/1", entity, String.class);
Also, you have #InjectMocks on your service class in your test, so you don't need this line
abc = new ABC();
MockitoAnnotations.initMocks(this) is already doing that for you.
#code_mechanic is right, issue is with the matchers supplied from the test method.
Rather than below in test method:
when(template.postForEntity(anyString(),any(),any())).thenReturn(new ResponseEntity<>(HttpStatus.OK));
Try this, provide url not anyString() :
when(template.postForEntity(ArgumentMatchers.endsWith("/abc/1"),any(),any())).thenReturn(new ResponseEntity<>(HttpStatus.OK));
This way it will figure out URL matching stub, it worked for me.
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).
I have #ControllerAdvice annotated class, which is handling BadRequestException extends RuntimeException exception.
Now suppose that I have endpoint:
#PostMapping(value = "/createAccount")
public ResponseEntity<CreateAccountResponse> createAccount(#RequestBody #Valid CreateAccountRequest createAccountRequest) {...}
In case of unwanted scenario, endpoint throws BadRequestException (with HTTP Status 400) which constructs error JSON object as following:
{
"errorCode": 123,
"errorMessage: "Failure reason"
}
Is there any way to document case like this using Spring REST Docs ?
This is example of my approach:
#Test
public void createAccountFailExample() {
RestDocumentationResultHandler docs = document("create-acc-fail-example",
responseFields(
fieldWithPath("errorCode").type("Integer").description("Error code"),
fieldWithPath("errorMessage").type("String").description("Error message")
)
);
org.assertj.core.api.Assertions.assertThatThrownBy(() -> this.mockMvc.perform(RestDocumentationRequestBuilders.post("/createAccount")
.contextPath("/account")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(new CreateAccountRequest("nameTest", "surnameTest"))))
.andExpect(status().isBadRequest())
.andDo(docs)).hasCause(new BadRequestException(ServiceError.SOME_FAIL_REASON));
}
In this case test passes, but no documentation (.adoc) files are created.
When I try something like this:
ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.post("/createAccount")
.contextPath("/account")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(new CreateAccountRequest("testName", "testSurname"))))
.andExpect(status().isBadRequest())
.andDo(docs);
test fails because NestedServletException was thrown caused by BadRequestException, and again there is no documentation created.
I managed to solve the problem following this answer. When exception handler is defined for MockMvc, my second approach works as expected.