I'm trying for more than an hour to test this class. It went so ugly of stubbing the whole components of the method etc. I'd love some advice how to make a better test or refactor the class to make it way easier to test. I could not figure out a way yet.
Class to Test
#Slf4j
public final class HistoryRestService {
static RestTemplate restTemplate = new RestTemplate();
public static Optional<List<History>> findLatestHistories() {
String url = buildUrl();
ResponseEntity<History[]> responseEntity = null;
try {
responseEntity = restTemplate.getForEntity(url, History[].class);
} catch (ResourceAccessException e) {
log.warn("No connection to History persistence. Please check if the history persistence started up properly");
return Optional.empty();
}
History[] histories = responseEntity.getBody();
return Optional.of(Arrays.asList(histories));
}
private static String buildUrl() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("http://");
stringBuilder.append("localhost");
stringBuilder.append(":8081");
stringBuilder.append("/history/get");
return stringBuilder.toString();
}
// For Testing
static void setRestTemplate(RestTemplate restTemplate) {
HistoryRestService.restTemplate = restTemplate;
}
}
Spock Test which fails
class HistoryRestServiceTest extends Specification {
def "test findLatestHistories"() {
given:
History mockedHistory = Mock()
HistoryRestService uut = new HistoryRestService()
History[] expected = [mockedHistory]
RestTemplate mockedRestTemplate = Stub()
ResponseEntity<History> mockedResponseEntity = Stub()
mockedResponseEntity.getBody() >> expected
mockedRestTemplate.getForEntity(_) >> mockedResponseEntity
uut.setRestTemplate(mockedRestTemplate)
when:
def actual = uut.findLatestHistories()
then:
actual.get() == expected
}
}
I'd suggest using real depedency-injection (spring/guice/cdi) instead of static variables.
Furthermore, you should think about what you want to test, is it the correct request and parsing of the network call, then write an integration test using something like mockserver or wiremock to have the whole stack. Or, if you are just concerned with the result handling, then you could move the code that interacts with RestTemplate into a separate method and use partial mocking to mock this method. I'd suggest to use the real integration test, but for the sake of an example this should work, but I didn't verify the code.
#Slf4j
public class HistoryRestService {
private final RestTemplate restTemplate;
public HistoryRestService() {
restTemplate = new RestTemplate();
}
public HistoryRestService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public Optional<List<History>> findLatestHistories() {
try {
return Optional.of(Arrays.asList(getLatestHistories(buildUrl())));
} catch (ResourceAccessException e) {
log.warn("No connection to History persistence. Please check if the history persistence started up properly");
return Optional.empty();
}
}
History[] getLatestHistories(String url) throws {
ResponseEntity<History[]> responseEntity = null;
responseEntity = restTemplate.getForEntity(url, History[].class);
return responseEntity.getBody()
}
private String buildUrl() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("http://");
stringBuilder.append("localhost");
stringBuilder.append(":8081");
stringBuilder.append("/history/get");
return stringBuilder.toString();
}
}
class HistoryRestServiceTest extends Specification {
#Subject
HistoryRestService uut = Spy()
def "test findLatestHistories"() {
given:
History[] expected = [mockedHistory]
when:
def actual = uut.findLatestHistories()
then:
actual.get() == expected
1 * uut.getLatestHistories(_ as String) >> expected
}
def "test findLatestHistories returns empty on exceptions"() {
given:
History[] expected = [mockedHistory]
when:
def actual = uut.findLatestHistories()
then:
!actual.present
1 * uut.getLatestHistories(_ as String) >> {throw new ResourceAccessException()}
}
}
Related
I am unable to get the mocked response from Feign Client. I provide below the code.
In the service class, it has been written like this.
public String getInfo(HttpServletRequest request, String id, String type) {
.... other code .....
try {
statusAsJsonString = myFeignClient.getStatus(cookie, id, type);
System.out.println("statusAsJsonString--------->"+statusAsJsonString);
ObjectNode node = new ObjectMapper().readValue(statusAsJsonString, ObjectNode.class);
if (node.has(CommonConstants.STATUS)) {
statusValue = node.get(CommonConstants.STATUS).asText();
}
} catch (FeignException fe) {
byte[] contents = fe.content();
String jsonContents = null;
if(contents != null) {
jsonContents = new String(contents);
}
statusValue = getErrorParsedStatusValue(jsonContents);
} catch (Exception ex) {
ex.printStackTrace();
}
log.debug("status: " + statusValue);
return statusValue;
}
In the unit test, I am trying to write in the following manner.
String responseBody = "[]";
when(myFeignClient.getStatus("cookievalue", "id", "SOme-Value")).thenReturn(responseBody);
I have also used, WireMock to achieve it.
wireMockServer.stubFor(WireMock.get("/rest/v1/somna/{id}/phase").withRequestBody(WireMock.equalToJson("{ \"name\": \"Phone\", \"initialStock\": 3}"))
.willReturn(WireMock.okJson(responseBody)));
The following piece of code is never covered and executed.
statusAsJsonString = myFeignClient.getStatus(cookie, id, type);
System.out.println("statusAsJsonString--------->"+statusAsJsonString);
Also the invocation of Feign client is inside a service method, first want to get the mocked result of that Feign client.
PLease help me.
I provide below my Feign CLient
#FeignClient(name = CommonConstants.FEIGN_CLIENT_NAME, url = "${feign.service.url}", primary = false)
public interface MyFeignClient {
#GetMapping(value = "/rest/v1/project/{id}/phaseName")
String getStatus(#RequestHeader("Cookie") String cookie,
#PathVariable("id") Stringid, #RequestParam("type") String type);
}
In my test class, I have added the followings.
#Autowired
private MyServiceImpl readyService = new MyServiceImpl();
#Mock
private MyFeignClient myFeignClient;
#ClassRule
public static WireMockServer wireMockServer = new WireMockServer(new WireMockConfiguration().port(8088));
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
httpServletRequest = Mockito.mock(HttpServletRequest.class);
ReflectionTestUtils.setField(someService, "cookieName", "cookieName");
wireMockServer.start();
}
Controller:
#ApiOperation(value = " update record", response = APIResponse.class)
#ApiResponses(value = {#ApiResponse(code =200, message = "OK"),
#ApiResponses(value = {#ApiResponse(code =500, message = "Internal server error"),
#ApiResponses(value = {#ApiResponse(code =404, message = "NO_RECORD")})
#PutMapping(value = "/update/{id}")
#ResponseBody
public ResponseEntity<APIResponse> updateRecord(HttpServletRequest request, #RequestBody RecordDTO input, #PathVariable(value="id") int code){
APIResponse response = null;
try{
response = service.updateRecord(code, input);
}
catch(JSONException e){
log.error("Error Parsing JSON");
response = new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR, ERROR_JSON_PARSING, ERROR);
}
return new ResponseEntity<>(response, HttpStatus.OK);
}
my test case foor controller:
#Test
public void update() throws Exception{
RecordDTO recordDto = new RecordDTO();
Object mapper = new ObjectMapper();
String value = mapper.writeValueAsString(StationDTO);
given(service.updateRecord(anyInt(), any(RecordDTO.class))).willThrow(JSONException.class);
mockMvc.perform(put(baseUrl + "/update/12")
.contentType(MediaType.APPLICATION_JSON).content(value))
.andExpect(status().isInternalservererror())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status",Matchers.is("INTERNAL_SERVER_ERROR")))
.andExpect(jsonPath("$.message",Matchers.is("ERROR_JSON_PARSING")))
.andExpect(jsonPath("$.resposeStatus",Matchers.is("ERROR")));
APIResponse response = new APIResponse(HttpStatus.OK, SUCCESS, SUCCESS, null);
given(service.updateRecord(anyInt(), any(RecordDTO.class))).willReturn(response);
mockMvc.perform(put(baseUrl + "/update/12")
.contentType(MediaType.APPLICATION_JSON).content(value))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status",Matchers.is("OK")))
.andExpect(jsonPath("$.message",Matchers.is("SUCCESS")))
.andExpect(jsonPath("$.resposeStatus",Matchers.is("SUCCESS")));
}
DTO:
public class RecordDTO{
private String id;
private String name;
private String number;
}
I am getting java.lang assertion error expected 500 but was 200. I don't what is wrong with the test case.. Is there any other way to write the test case? Also can you recommend any platform from where i can gain knowledge of how to write test cases then do comment down. Thanks for the help!
Seems like your mocked service is not injecting into your controller.
Alternative solution (I assume you use Spring-Boot):
DisableAutowireRequireInitializer. This will prevent to load all dependencies inside your Controller.
Create inside your ControllerTest inner class: private static ServiceImplMock entends ServiceImpl
Now, override updateRecord method inside ServiceMock to do your testing cases
#Override
public APIResponse updateRecord(int code, RecordDTO input) throws JSONException {
if(code == 12) throw new JSONException(...)
else your_business_logic
}
Now, add this ServiceImplMock into your #SpringBootTest
#SpringBootTest(classes = {
Controller.class,
ControllerTest.ServiceImplMock.class,
...
})
#AutoConfigureMockMvc
#ContextConfiguration( initializers = {DisableAutowireRequireInitializer.class })
class ControllerTest {
Now, your test cases should work (Remove given(...).willThrow(...); since we don't need it anymore)
Also can you recommend any platform from where i can gain knowledge of how to write test cases then do comment down
https://www.baeldung.com/junit
https://www.baeldung.com/spring-boot-testing
https://mkyong.com/spring-boot/spring-boot-junit-5-mockito/
I'm new to testing and Mockito. However, not so new in working Spring.
I have a service layer implementation of exportResource(String id, String fileType, Class resourceClass) throws MyCustomEx1, MyCustomEx2. My assignment is to create a unit test with Mockito for exportResource() which is downloading a file directly from browser or throws exception if for some reason reaching to ResponseEntity return statement is not successfull.
Here is a rough overview of a service layer class where exportResource() is living;
#Service
#AllArgsConstructor
public class ExportImportServiceImpl implements ExportImportService {
private final FhirRepository fhirRepository;
private final CtsConfig ctsConfig;
#Override
public ResponseEntity<byte[]> exportResource(String id, String fileType, Class resourceClass) throws throws MyCustomEx1, MyCustomEx2 {
if (fileType == null ) throw new MyCustomEx1();
Bundle bundle = (Bundle) fhirRepository.resourcesWithCriterion(resourceClass, DaoConstants.ID, null).get();
if (bundle != null && bundle.hasEntry()) {
Optional<Bundle.BundleEntryComponent> resource =
bundle.getEntry()
.stream()
.filter(filter -> resourceClass.isInstance(filter.getResource()))
.findFirst();
if (resource.isPresent()) {
IBaseResource castedResource = (IBaseResource) resourceClass.cast(resource.get().getResource());
IParser parser = null;
MediaType mt = null;
if (fileType.equalsIgnoreCase("json")){
mt = MediaType.APPLICATION_JSON;
parser = fhirRepository.jsonParser();
} else if (fileType.equalsIgnoreCase("xml")){
mt = MediaType.APPLICATION_XML;
parser = fhirRepository.xmlParser();
} else throw new MyCustomEx1();
parser.setPrettyPrint(true);
var serializedResource = parser.encodeResourceToString(castedResource);
return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,"attachment;" + "filename=" + resourceClass.getSimpleName() + "-" + castedResource.getId() + "." + fileType)
.contentType(mt)
.contentLength(serializedResource.getBytes(StandardCharsets.UTF_8).length)
.body(serializedResource.getBytes(StandardCharsets.UTF_8));
}
}
throw new MyCustomEx2();
}
//other implementations here...
}
I do understand (by wastching some tutorials etc) that we can use Mockito to mock external services/repositories, to verify if some method is getting called, to mock some return values etc. But I'm confused about if-else parts in my business logic-code. Namely, the part:
if (bundle != null && bundle.hasEntry()) {
Optional<Bundle.BundleEntryComponent> resource =
bundle.getEntry()
.stream()
.filter(filter -> resourceClass.isInstance(filter.getResource()))
.findFirst();
if (resource.isPresent()) {
IBaseResource castedResource = (IBaseResource) resourceClass.cast(resource.get().getResource());
IParser parser = null;
MediaType mt = null;
if (fileType.equalsIgnoreCase("json")){
mt = MediaType.APPLICATION_JSON;
parser = fhirRepository.jsonParser();
} else if (fileType.equalsIgnoreCase("xml")){
mt = MediaType.APPLICATION_XML;
parser = fhirRepository.xmlParser();
} else throw new ByException(ByErrorCode.ERR_BY_1013);
parser.setPrettyPrint(true);
var serializedResource = parser.encodeResourceToString(castedResource);
I don't understand how to write test to handle if-else part. I mean, isn't too simple to test something like that?
Here is what I (sadly) got:
#Test
void exportCodeSystem() throws Exception {
String id = "32";
verify(this.fhirRepository.resourcesWithCriterion(ValueSet.class, DaoConstants.ID, null));
Mockito.when(
controller.exportCodeSystem(Mockito.anyString(),Mockito.anyString())
).thenReturn(ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).build());
RequestBuilder request = MockMvcRequestBuilders.get("/url-to-controller")
.param("id","32")
.param("file-type", "json");
MvcResult result = mockMvc.perform(request).andReturn();
Assertions.assertFalse(result.getResponse().getStatus() == 200);
}
This throws some error. But that is not important as to how to write meaningful unit test in this case? Or even better, how to write code which can be easily unit-tested?
UPDATE 1
This is how my test looks now:
#Test
void exportCodeSystem() throws Exception {
CompletableFuture<Bundle> completedFuture = CompletableFuture.completedFuture(this.codeSystemBundle);
Mockito.when(this.fhirRepository.resourcesWithCriterion(ArgumentMatchers.<Class<CodeSystem>>any(),
ArgumentMatchers.<ICriterion<? extends IParam>>any(),
eq(null)))
.thenReturn(completedFuture);
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.
get("/code-system/export")
.param("id", "32").param("file-type", "json"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentType("application/json")).andReturn();
Assertions.assertEquals(result.getResponse().getStatus(), HttpStatus.OK.value());
}
I keep getting NPE on the line where MvcResult result .. is. What I want to achive with test? I don't know to be honest. I'm trying to test in a some meaningful way, but I don't think I understand how to achieve that.
This is how controller looks like:
#AllArgsConstructor
#RestController
#RequestMapping("/code-system")
public class CodeSystemController {
private final ExportImportService exportImportService;
#GetMapping("/export")
public boolean exportCodeSystem(#RequestParam("id") String id, #RequestParam("file-type") String fileType) throws IOException, ByException{
return this.exportImportService.exportResource(id, fileType, CodeSystem.class);
}
}
What do you want to test? Would your test be unit test or integration test? Which bean do you want to unit test - controller or the service?
If you want to test the controller via mockMvc you can do this:
#Autowired
private MockMvc mockMvc;
#Autowired
ExportImportService exportImportService;
#Test
public void yourTest() throws Exception {
byte[] expectedBytes = ...;
when(exportImportService.exportResource(id, fileType, CodeSystem.class)).thenReturn(ResourceEntity.ok(expectedBytes ));
mockMvc.perform(post("/code-system/export").param("id", "id").param("file-type", "json")).andExpect(content().bytes(expectedBytes ));
verify(exportImportService).exportResource("id", "json", CodeSystem.class);
}
Or if you want to write an integration test you should mock the repo instead of the service:
#MockBean
FhirRepository fhirRepository
#MockBean // or #Mock, #Spy, #SpyBean depends on your fhirRepository implementation.
IParser parser;
#Test
public void yourTest() throws Exception {
byte[] expectedBytes = ...;
Bundle bundle = ...;
when(fhirRepository.resourcesWithCriterion(Mockito.any(), Mockito.any(), Mockito.any()).thenReturn(bundle)
when(fhirRepository.jsonParser()).thenReturn(parser);
when(parser.encodeResourceToString(Mockito.any())).thenReturn(new String(expectedBytes));
mockMvc.perform(post("/code-system/export").param("id", "id").param("file-type", "json")).andExpect(content().bytes(expectedBytes));
}
I am trying to write a unit test for the below method , where it is invoking the Lambda.
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.model.InvokeRequest;
import software.amazon.awssdk.services.lambda.model.InvokeResponse;
public class RoutingService {
private LambdaClient lambdaClient;
private String env;
private String region;
public RoutingService(){
this.env = System.getenv(ENVIRONMENT);
this.region = System.getenv(REGION);
lambdaClient = LambdaClient.builder().build();
}
public RoutingResponse invokeLambda(InvokeLambda request) throws IOException {
RoutingResponse invokeResponse = new RoutingResponse();
String payload = createPayload(request); // Some utility method
String routingURI = getRoutingURI() ; // Another utility method
SDKBytes payloadBytes = SdkBytes.fromUtf8String(payload);
InvokeRequest invokeRequest = InvokeRequest.builder()
.functionName(routingUri)
.payload(payloadBytes)
.invocationType(INVOCATION_TYPE) // INVOCATION_TYPE is caonstant = RequestResponse
.build();
try{
InvokeResponse result;
result = lambdaClient.invoke(invokeRequest); // Was trying to Mock this
String resultString = result.payload().asUtf8String();
invokeResponse = new ObjectMapper().readValue(resultString, RoutingResponse.class);
}catch(AWSServiceExceptuion ex) {
logger.error(new ErrorEvent(ex.awsErrorDetails.errorMessage(), ex.getMessage(), routingUri));
invokeResponse.setStatusCode(ex.statusCode());
invokeResponse.setBody(ex.getMessage());
}
return response;
}
I am trying to write unit test case for this method as below (Mocking the LambdaClient) .
But still it is trying to call the invoke method of Lambda Client.
Means Mocking is not working properly .
public class RoutingServiceTest {
#Mock
private LambdaClient lambdaClient;
#BeforeEach
void setup(){
MockitoAnnotations.initMocks(this);
}
#Test
void invokeLambda() throws IOException, JSONException {
Map<String,String> queryStringParameters = new HashMap<>();
queryStringParameters.put("test","GOOG");
String[] cookies = {"cookiea", "cookieb"};
Map<String,String> headers = new HashMap<>();
headers.put("header1","valuex");
headers.put("header2","valuey");
InvokeLambda fakeRequest = new InvokeLambda.InvokeLambdaBuilder()
.cdnToken("cdn-token").applicationName("eaf-4137-demo")
.service("quote").method("GET")
.path("QuoteService/getQuote")
.queryString(queryStringParameters)
.headers(headers)
.cookies(cookies)
.body("body")
.requestId("request-id")
.build();
InvokeResponse fakeResponse = InvokeResponse.builder().build(); // This class belongs to AWS library
Mockito.when(lambdaClient.invoke(Mockito.any(InvokeRequest.class))).thenReturn(fakeResponse); // This is not working correctly ..
RoutingService routingService = new RoutingService();
RoutingResponse response = routingService.invokeLambda(fakeRequest);
Assertions.assertTrue(response.getStatusCode()==200);
}
}
When I run this test , an Exception is thrown in the try{} block of 'RoutingService' class , because we are passing a dummy "cdn token"
in fakeResponse.
Ideally , it should not invoke the lambda as we are Mocking it.
Please suggest what am I doing wrong here ?
How to correct this ?
You don't have your lambdaClient mock in your instance of RoutingService. In dead your mock is created in your test setup but you create a new instance of RoutingService that will create its own instance of lambdaClient not the mock you created in your test.
I suggest to update your RoutingService to allow constructor dependency injection:
public RoutingService(final LambdaClient lambdaClient){
this.env = System.getenv(ENVIRONMENT);
this.region = System.getenv(REGION);
this.lambdaClient = lambdaClient;
}
And test like:
RoutingService routingService = new RoutingService(lambdaClient);
RoutingResponse response = routingService.invokeLambda(fakeRequest);
In my spring boot project, one of my Service depends on external service like Amazon. I am writing the integration testing of the Controller classes. So, I want to mock the method in the AmazonService class(as it depends on third party API). The method is void with a single Long argument and can throw a custom application-specific exceptions.
The method is as follows:-
class AmazonService{
public void deleteMultipleObjects(Long enterpriseId) {
String key = formApplicationLogokey(enterpriseId,null);
List<S3ObjectSummary> objects = getAllObjectSummaryByFolder(key);
List<DeleteObjectsRequest.KeyVersion> keys = new ArrayList<>();
objects.stream().forEach(object->keys.add(new DeleteObjectsRequest.KeyVersion(object.getKey())));
try{
DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(this.bucket).withKeys(keys);
this.s3client.deleteObjects(deleteObjectsRequest);
log.debug("All the Application logos deleted from AWS for the Enterprise id: {}",enterpriseId);
}
catch(AmazonServiceException e){
throw new AppScoreException(AppScoreErrorCode.OBJECT_NOT_DELETED_FROM_AWS);
}
}}
class Test
class Test
{
#Autowired
AmazonServiceImpl amazonService;
#Autowired
EnterpriseService enterpriseService;
#Before
public void init()
{
amazonService = Mockito.mock(AmazonServiceImpl.class);
Mockito.doNothing().when(amazonService).deleteMultipleObjects(isA(Long.class));
}
#Test
public void testDeleteEnterprise(){
setHeaders();
EnterpriseDTO enterpriseDTO = createEnterpriseEntity(null,"testDeleteEnterpriseName3",null,null,null);
String postUrl = TestUrlUtil.createURLWithPort(TestConstants.ADD_ENTERPRISE,port);
HttpEntity<EnterpriseDTO> request1 = new HttpEntity<>(enterpriseDTO,headers);
ResponseEntity<EnterpriseDTO> response1 = restTemplate.postForEntity(postUrl,request1,EnterpriseDTO.class);
assert response1 != null;
Long enterpriseId = Objects.requireNonNull(response1.getBody()).getId();
String url = TestUrlUtil.createURLWithPort(TestConstants.DELETE_ENTERPRISE,port)+File.separator+enterpriseId;
HttpEntity<EnterpriseDTO> request = new HttpEntity<>(null, headers);
ResponseEntity<Object> response = restTemplate.exchange(url,HttpMethod.DELETE,request,Object.class);
Assert.assertEquals(Constants.ENTERPRISE_DELETION_SUCCESS_MESSAGE,response.getBody());
}
}
class EnterpriseResource
class EnterpriseResource
{
#DeleteMapping("/enterprises/{enterpriseId}")
public ResponseEntity<Object> deleteEnterprise(#PathVariable Long enterpriseId) {
log.debug("REST request to delete Enterprise : {}", enterpriseId);
enterpriseService.delete(enterpriseId);
return ResponseEntity.badRequest().body(Constants.ENTERPRISE_DELETION_SUCCESS_MESSAGE);
}
}
class EnterpriseServiceImpl
class EnterpriseServiceImpl
{
#Override
public void delete(Long enterpriseId) {
log.debug("Request to delete Enterprise : {}", enterpriseId);
enterpriseRepository.deleteById(enterpriseId);
amazonService.deleteMultipleObjects(enterpriseId);
}
}
I have tried various approaches to Mock this method but it didn't work and control is going inside this method during debugging. I want to do nothing in this method during testing.
I have tried the various approaches like throw(), doNothing(), spy() etc.
Please help what is missing here?
Thanks