I am new to spring framework.I have started using Retryable annotation in my mvc application.I have added #EnableRetry on my config class.
#Configuration
#EnableScheduling
#EnableRetry
class ApplicationConfig {
I have my MachinesContainer class in which I am calling some other REST APIs.On that method I have used #Retryable annotation with configuration provided to that.
#Retryable(value = {NullPointerException.class},maxAttempts = 3,backoff = #Backoff(2000))
public static void getMachineContainer(ResponseEntity<MachinesContainer> machinesContainer,String ipsByGeoUrl,HttpEntity<?> requestEntity) throws Exception {
if(machinesContainer==null) {
throw new NullPointerException();
}
machinesContainer = restTemplate
.exchange(ipsByGeoUrl, HttpMethod.POST,
requestEntity, MachinesContainer.class);
}
It directly calls to exception instead of calling "getMachineContainer" 3 times.
#Component
public class Query{
#Override
public MachinesContainer getIpsByGeo(String city, String state, String country) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
String query = bundle.getString("ipsByGeo.sqlQuery");
// Build Map with params to IPsByGeo query
HashMap<String, String> args = new HashMap<String, String>();
args.put("cityKey", city.trim().toUpperCase());
args.put("countryKey", country.trim());
if (state != null && state.length() >= 1) {
args.put("stateClause", " and r.state='" + state.trim() + "'");
} else {
args.put("stateClause", "");
}
//Generate query with values
StrSubstitutor sub = new StrSubstitutor(args,"$","$");
query = sub.replace(query);
HttpEntity<?> requestEntity = new HttpEntity<String>(query, headers);
String environment = System.getenv(SERVICE_ENVIRONMENT);
logger.debug("getIpsByGeo service environment is:{}",environment);
String ipsByGeoUrl = <Some API URL>;
try {
ResponseEntity<MachinesContainer> machinesContainer = null;
getMachineContainer(machinesContainer,ipsByGeoUrl,requestEntity);
return machinesContainer.getBody();
}
catch (Exception e) {
throw e;
}
}
}
Please suggest some solutions.
I am using spring version - 4.2.3.RELEASE
Java version - 1.8
spring-retry - 1.2.1.RELEASE
spring-aop - 4.2.5.RELEASE
aspectjweaver - 1.8.8
Couple of things
1. #Retryable ia based on Spring AOP which uses proxy, and hence it doesn't work with private method calls(I mean methods in the same class)
2. Same applies to static methods.
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();
}
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
I need to know how Spring boot maps the request parameters in the URL to a POJO at run time.
Here is an example URL with parameters
http://localhost:8080/api/public/properties?serviceType.in=SALE&title.contains=some text&price.greaterOrEqualThan=500&price.lessOrEqualThan=50000&propertyType.in=HOUSE&locationId.in=1,2&landSize.greaterOrEqualThan=100&landSize.lessOrEqualThan=1000&bedrooms.greaterOrEqualThan=2&bedrooms.lessOrEqualThan=5&bathrooms.greaterOrEqualThan=1&bathrooms.lessOrEqualThan=3&ageType.in=BRAND_NEW
I have a number of Criteria classes that all extends PropertyCriteria class. To give an example, if the request contains no parameters, I want the controller to use the PropertyCriteria. If the request contains a bedrooms parameter, I want the controller to use the HousePropertyCriteria and so on. See controller method example below.
#GetMapping("/public/properties")
public ResponseEntity<List<Property>>
getAllPropertiesNested(HttpServletRequest request) {
if (condition1 == true) {
EntityOnePropertyCriteria c1 = new EntityOnePropertyCriteria();
//populate c1 using request object
} else {
EntityTwoPropertyCriteria c2 = new EntityTwoPropertyCriteria();
//populate c2 using request object
}
}
Two ways of doing this manually:
1) I wonder if in your project you have access to HttpServletRequest object. If that is the case, you can use the method request.getParameter(nameParam) to populate the object that you need.
2) Use beanutils library and using the method
BeanUtils.copyProperties(dest, source)
Using "#RequestParam Map source" in your controller and replacing the dest object you want fill
I found the answer on this link.
public static void applyMapOntoInstance(Object instance, Map properties) {
if (properties != null && !properties.isEmpty()) {
BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(instance);
beanWrapper.setAutoGrowNestedPaths(true);
for (Iterator<?> iterator = properties.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<String, ?> entry = (Map.Entry<String, ?>) iterator.next();
String propertyName = entry.getKey();
if (beanWrapper.isWritableProperty(propertyName)) {
beanWrapper.setPropertyValue(propertyName, entry.getValue());
}
}
}
}
My controller method now looks like:
#GetMapping("/public/properties")
#Timed
public ResponseEntity<List<Property>> getAllPropertiesNested(HttpServletRequest request) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
HttpHeaders headers = null;
if (requestContains("bedrooms", request)) {
HousePropertyCriteria housePropertyCriteria = new HousePropertyCriteria();
applyMapOntoInstance(housePropertyCriteria, request.getParameterMap());
Page<HouseProperty> page = housePropertyQueryService.findByCriteriaNested(housePropertyCriteria, pageable);
headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/public/properties");
return new ResponseEntity(page.getContent(), headers, HttpStatus.OK);
} else {
Page<Property> page = propertyQueryService.findByCriteriaNested(criteria, pageable);
headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/public/properties");
return new ResponseEntity(page.getContent(), headers, HttpStatus.OK);
}
}
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()}
}
}
I'm stuck with the following problem: in order to access an online API, I need to get authenticated. Right now, I do everything in my own code:
Call the token URL for a bearer token
Get the bearer token
Call the real service with the bearer token
Get the result
Here's the code:
#RestController
public class RandomController {
private final Random random;
public RandomController(Random random) {
this.random = random;
}
#RequestMapping(value = "/get", method = GET)
public int random(#RequestParam(value = "limit", defaultValue = "100") int limit) {
String bearerToken = getBearerToken();
int[] bounds = getBounds(bearerToken);
return computeRandom(bounds[0], bounds[1]);
}
private String getBearerToken() {
RestTemplate tokenTemplate = new RestTemplate();
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("client_id", "my id");
body.add("client_secret", "my secret");
body.add("grant_type", "client_credentials");
HttpHeaders headers = new HttpHeaders();
headers.add("Accept", "application/json");
HttpEntity<?> entity = new HttpEntity<>(body, headers);
ResponseEntity<String> res = tokenTemplate.exchange(
"https://bearer.token/get", POST, entity, String.class);
Map<String, Object> map = new BasicJsonParser().parseMap(res.getBody());
return (String) map.get("access_token");
}
private int[] getBounds(String bearerToken) {
RestTemplate configurationTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + bearerToken);
HttpEntity<?> entity = new HttpEntity<>(headers);
ResponseEntity<String> res = configurationTemplate.exchange(
"https://configurations.com/bounds", HttpMethod.GET, entity, String.class);
Map<String, Object> map = new BasicJsonParser().parseMap(res.getBody());
Map<String, Long> value = (Map<String, Long>) map.get("value");
int lowerBound = value.get("lower").intValue();
int upperBound = value.get("upper").intValue();
return new int[]{lowerBound, upperBound};
}
private int computeRandom(int lowerBound, int upperBound) {
int difference = upperBound - lowerBound;
int raw = random.nextInt(difference);
return raw + lowerBound;
}
}
It works, but I'm wasting a call to the token URL at every call. This is how I'd like it to work:
Call the real service
If getting a 401
Call the token URL for a bearer token
Get the bearer token
Recall the service with the bearer token
Get the result
I could do that in my code, but I'm already using Spring Boot. I'm wondering how to achieve that. Is there an existing filter, interceptor, whatever?
Thanks for your insights.
Try using OAuth2RestTemplate from Spring Security. It should take care of getting token and caching it if possible. It should be configured in the config file and injected everywhere you make call to the API.
A jny mentions, the class to use is OAuth2RestTemplate. However, its constructor requires an implementation of OAuth2ProtectedResourceDetails.
Several implementation are available, the one that uses client_credential is ClientCredentialsResourceDetails. I was hoping that just adding #EnableOAuth2Client to my configuration class and configuring the required information in the application.yml would be enough:
security:
oauth2:
client:
grant-type: client_credentials
client-id: my id
client-secret: my secret
access-token-uri: https://bearer.token/get
Unfortunately, it doesn't work. The reason can be found in class OAuth2RestOperationsConfiguration:
#Configuration
#ConditionalOnClass(EnableOAuth2Client.class)
#Conditional(OAuth2ClientIdCondition.class)
public class OAuth2RestOperationsConfiguration {
#Configuration
#ConditionalOnNotWebApplication
protected static class SingletonScopedConfiguration {
#Bean
#ConfigurationProperties("security.oauth2.client")
#Primary
public ClientCredentialsResourceDetails oauth2RemoteResource() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
return details;
}
...
}
...
}
It seems the Spring framework assumes - incorrectly, that only non-web application can use the client credentials authentication. Whereas in my case, that's the only way the provider offers.
The relevant snippet can be copy-pasted though :-)
My final code looks like:
#SpringBootApplication
public class RandomApplication {
public static void main(String[] args) {
SpringApplication.run(RandomApplication.class, args);
}
#Bean
#ConfigurationProperties("security.oauth2.client")
ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}
#Bean
OAuth2RestTemplate oAuth2RestTemplate() {
return new OAuth2RestTemplate(clientCredentialsResourceDetails());
}
#Bean
Random random() {
return new SecureRandom();
}
}
#RestController
public class RandomController {
private final OAuth2RestTemplate oAuth2RestTemplate;
private final Random random;
public RandomController(OAuth2RestTemplate oAuth2RestTemplate, Random random) {
this.oAuth2RestTemplate = oAuth2RestTemplate;
this.random = random;
}
#RequestMapping(value = "/get", method = GET)
public int random() {
int[] bounds = getBounds();
return computeRandom(bounds[0], bounds[1]);
}
private int[] getBounds() {
ResponseEntity<String> res = oAuth2RestTemplate.getForEntity(
"https://configurations.com/bounds", String.class);
Map<String, Object> map = new BasicJsonParser().parseMap(res.getBody());
Map<String, Long> value = (Map<String, Long>) map.get("value");
int lowerBound = value.get("lower").intValue();
int upperBound = value.get("upper").intValue();
return new int[]{lowerBound, upperBound};
}
private int computeRandom(int lowerBound, int upperBound) {
int difference = upperBound - lowerBound;
int raw = random.nextInt(difference);
return raw + lowerBound;
}
}
Notice that the controller gets much less boilerplate because I replaced the RestTemplate with OAuth2RestTemplate.