spring-mvc: how to test Rx responses with mockMvc? - java

my controller is:
import rx.Single;
...
#GetMapping
Single<List<MyType>> fetchFromDB() {
return Single
.fromCallable(() -> dao.fetch())
.subscribeOn(Schedulers.io());
}
and it works perfectly. but i can't tests it. i tried:
MvcResult asyncResult = mvc.perform(get("/")).andReturn()
String result = mvc
.perform(asyncDispatch(asyncResult))
.andReturn().getResponse().getContentAsString()
but it fails with:
java.lang.IllegalStateException: Async result for handler [rx.Single<java.util.List<MyType>> MyController.fetchFromDB()] was not set during the specified timeToWait=-1
at org.springframework.test.web.servlet.DefaultMvcResult.getAsyncResult(DefaultMvcResult.java:145)
at org.springframework.test.web.servlet.DefaultMvcResult.getAsyncResult(DefaultMvcResult.java:121)
at org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch(MockMvcRequestBuilders.java:246)
at MyControllerSpec.should fetch from db...
so: how to test rx.Single with spring mvc?

i found the answer. when you create your mockMvc object, add a handler for Single:
return MockMvcBuilders.standaloneSetup(controller)
.setCustomReturnValueHandlers(new SingleReturnValueHandler())

if you use MockMvcBuilders.webAppContextSetup(context) instead, you can add the handler in your WebMvcConfigurerAdapter:
public class Config extends WebMvcConfigurerAdapter {
#Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
returnValueHandlers.add(new SingleReturnValueHandler());
}
}

Related

Apache Camel aggregation completion not working

I've configured a route to extract some data from exchanges and aggregate them; here is simple summary:
#Component
#RequiredArgsConstructor
public class FingerprintHistoryRouteBuilder extends RouteBuilder {
private final FingerprintHistoryService fingerprintHistoryService;
#Override
public void configure() throws Exception {
from("seda:httpFingerprint")
.aggregate( (AggregationStrategy) (oldExchange, newExchange) -> {
final FingerprintHistory newFingerprint = extract(newExchange);
if (oldExchange == null) {
List<FingerprintHistory> fingerprintHistories = new ArrayList<>();
fingerprintHistories.add(newFingerprint);
newExchange.getMessage().setBody(fingerprintHistories);
return newExchange;
}
final Message oldMessage = oldExchange.getMessage();
final List<FingerprintHistory> fingerprintHistories = (List<FingerprintHistory>) oldMessage.getBody(List.class);
fingerprintHistories.add(newFingerprint);
return oldExchange;
})
.constant(true)
.completionSize(aggregateCount)
.completionInterval(aggregateDuration.toMillis())
.to("direct:processFingerprint")
.end();
from("direct:processFingerprint")
.process(exchange -> {
List<FingerprintHistory> fingerprintHistories = exchange.getMessage().getBody(List.class);
fingerprintHistoryService.saveAll(fingerprintHistories);
});
strong text
}
}
The problem is aggregation completion never works for example this is a sample of my test:
#SpringBootTest
class FingerprintHistoryRouteBuilderTest {
#Autowired
ProducerTemplate producerTemplate;
#Autowired
FingerprintHistoryRouteBuilder fingerprintHistoryRouteBuilder;
#Autowired
CamelContext camelContext;
#MockBean
FingerprintHistoryService historyService;
#Test
void api_whenAggregate() {
UserSearchActivity activity = ActivityFactory.buildSampleSearchActivity("127.0.0.1", "salam", "finger");
Exchange exchange = buildExchange();
exchange.getMessage().setBody(activity);
ReflelctionTestUtils.setField(fingerprintHistoryRouteBuilder, "aggregateCount", 1);
ReflectionTestUtils.setFiled(fingerprintHistoryRouteBuilder, "aggregateDuration", Duration.ofNanos(1));
producerTemplate.send(FingerprintHistoryRouteBuilder.FINGERPRINT_HISTORY_ENDPOINT, exchange);
Mockito.verify(historyService).saveAll(Mockito.any());
}
Exchange buildExchange() {
DefaultExchange defaultExchange = new DefaultExchange(camelContext);
defaultExchange.setMessage(new DefaultMessage(camelContext));
return defaultExchange;
}
}
with the following result:
Wanted but not invoked: fingerprintHistoryService bean.saveAll(
);
I build this simplified example, and the test passes, so it looks like your usage of aggregate is probably correct.
Have you considered that your Mockito.verify() call is happening before the exchange finishes routing? You could test this by removing the verify call and adding a .log() statement to the FINGERPRINT_PROCESS_AGGREGATION route. If you see the log output during execution, you know the exchange is being routed as you expect. If this is the case, then your verify() call needs to be able to wait for the exchange to finish routing. I don't use mockito much, but it looks like you can do this:
Mockito.verify(historyService, timeout(10000)).saveAll(Mockito.any());

Consecutive same Rest API Call using Spring Boot

I want to call a Rest API using springboot till a field in the response (hasMoreEntries) is 'Y'. Currently, am simply using a while loop and checking the response for the flag and calling the API again. PFB pseudocode. Is there any other efficient way to do this OR what is the best way.
String hasMoreEntries="Y";
while(!hasMoreEntries.equals("N"))
{
response = \\PERFORM REST SERVICE CALL HERE
hasMoreEntries=respone.body().getHasMoreEntries();
}
You can use Spring Retry mechanism.
For example you can create RetryTemplate bean with custom exception ResponseValidateException that is thrown when the response is invalid:
#Bean
public RetryTemplate retryTemplate() {
return RetryTemplate.builder()
.retryOn(ResponseValidateException.class)
.infiniteRetry()
.fixedBackoff(2000L)
.build();
}
And your service:
#Service
public class YourService {
#Autowired
private RetryTemplate retryTemplate;
public void bar() {
final ResponseEntity<SomeObject> entity = retryTemplate.execute(retryContext -> {
final ResponseEntity<SomeObject> response = null;
if ("Y".equals(response.getBody().getHasMoreEntries())) {
return response;
} else {
throw new ResponseValidateException();
}
});
}
}
You can also look at custom RetryPolicy (for example CircuitBreakerRetryPolicy) classes to add them in your RetryTemplate bean for your cases.

Testing Spring Hateoas Application with RepresentationModelAssembler

I'm trying to test my Spring Hateoas application, more specifically the controllers, using Springs #WebMvcTest. But I'm having problems injecting my custom RepresentationModelAssembler into the test.
First a bit of my setup:
I'm using a custom RepresentationModelAssembler to turn my DB-Models into DTOs, which have all necessary links added.
The RepresentationModelAssembler:
#Component
public class BusinessUnitAssembler implements RepresentationModelAssembler<BusinessUnit, BusinessUnitDto> {
private final Class<BusinessUnitController> controllerClass = BusinessUnitController.class;
private final BusinessUnitMapper businessUnitMapper;
public BusinessUnitAssembler(BusinessUnitMapper businessUnitMapper) {
this.businessUnitMapper = businessUnitMapper;
}
#Override
public BusinessUnitDto toModel(BusinessUnit entity) {
return businessUnitMapper.businessUnitToDto(entity)
.add(linkTo(methodOn(controllerClass).findById(entity.getId())).withSelfRel());
}
}
The BusinessUnitMapper used here is a Mapstruct mapper, which is injected by spring. In my Service I use the BusinessUnitAssembler to turn my DB-Models into DTOs, example Service method:
public Page<BusinessUnitDto> findAll(Pageable pageable) {
Page<BusinessUnit> pagedResult = businessUnitRepository.findAll(pageable);
if (pagedResult.hasContent()) {
return pagedResult.map(businessUnitAssembler::toModel);
} else {
return Page.empty();
}
}
This is how I'm doing the testing currently:
#WebMvcTest(controllers = BusinessUnitController.class)
public class BusinessUnitControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BusinessUnitService businessUnitService;
private BusinessUnitMapper mapper = Mappers.getMapper(BusinessUnitMapper.class);
private BusinessUnitAssembler assembler = new BusinessUnitAssembler(mapper);
#Test
public void getAllShouldReturnAllBusinessUnits() throws Exception {
List<BusinessUnitDto> businessUnits = Stream.of(
new BusinessUnit(1L, "Personal"),
new BusinessUnit(2L, "IT")
).map(businessUnit -> assembler.toModel(businessUnit)).collect(Collectors.toList());
when(businessUnitService.findAll(Pageable.ofSize(10))).thenReturn(new PageImpl<>(businessUnits));
mockMvc.perform(get("/businessUnits").accept(MediaTypes.HAL_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.*", hasSize(3)))
// ... do more jsonPath checking
}
}
But I'd like to have Spring inject the BusinessUnitAssembler, instead of constructing it myself. I've tried #Importing BusinessUnitAssembler as well as the BusinessUnitMapper and I've also tried it by using a custom #Configuration but I just couldn't get it to work.
So my Question is: How can I let Spring inject my BusinessUnitAssembler into the test for me instead of assembling it myself?
Additional Question: Is it valid to combine the Mapping from Database Entity to DTO in the RepresentationModelAssembler or should those two steps be kept seperate from each other?

call another service after sending the success response in spring boot?

The first controller is sending the success response after that it will call another method after sending the response.
I need to call m1()method after return the response
#RequestMapping(value = {"/hello"}, method = POST, produces = { "application/json" })
public Response getAllData(#RequestBody String request){
return new ResponseEntity<>("Hello World");
}
public void m1(){
}
The simple trick is to use try...finally.
try{
return new Response();
} finally {
//do after actions
}
'finally' will always execute after the try block no matter there is a return statement in it.
Example for the Spring AspectJ using #AfterReturning advice
#Aspect
#Component
public class A {
/*
* AfterReturning advice
*/
#AfterReturning("execution(* com.package.ClassName.getAllData(..))")
public void m1(JoinPoint joinPoint) {
}
}
You need to add #EnableAsync in your configuration class or main class
then create another service class encapsulating an Async method m1
In your class add the statement below:
asyncServiceImpl.m1();
#Service
public class AsyncServiceImpl {
#Async
public CompletableFuture<String> m1() {
// Your logic here
return CompletableFuture.completedFuture(String.valueOf(Boolean.FALSE));
}
}
you can use eventListener for creating event.
And catch it in public method with EventListener annotation.
https://www.baeldung.com/spring-events
The most simple and reliable way - run a thread. Try-final isn't good at all.
But the best solution is - throw out that SB and use pure JEE servlets to invoke all that you need (JSON, JPA, BM transactions) in the client's request's thread, so you will never get stuck like that.

Test RestTemplate Interceptors

i've implemented a Interceptors for RestTemplate (RestTemplateInterceptor.class):
#Autowired
private RestConfigurations configurations;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.additionalInterceptors((ClientHttpRequestInterceptor) (httpRequest, bytes, clientHttpRequestExecution) -> {
HttpHeaders headers = httpRequest.getHeaders();
headers.add("X-API-KEY", configurations.getApiKey());
return clientHttpRequestExecution.execute(httpRequest,bytes);
}).build();
}
Now I want to test it. :)
One try looks like that:
#ExtendWith(SpringExtension.class)
#RestClientTest({RestTemplateInterceptor.class, RestConfigurations.class})
#AutoConfigureMockMvc
#SpringBootTest
class MyTestClass {
public static final String URL = "http://testMe.com/";
#Autowired
MockRestServiceServer mockServer;
#Autowired
RestTemplateBuilder restTemplateBuilder;
#Test
#DisplayName("Should include header X-API-KEY")
void headerShouldContainsXApiKey() throws Exception {
mockServer.expect(requestTo(URL)).andRespond(withSuccess("Hello world", TEXT_PLAIN));
String body = restTemplateBuilder.build().exchange(URL//
, GET, //
null, //
String.class).getBody();
assertThat(body).isEqualTo("Hello world");
mockServer.expect(header("X-API-KEY", ""));
}
But i failed with:
java.lang.AssertionError: No further requests expected: HTTP GET http://test.hornbach.de/
0 request(s) executed.
Anyone a hint? Thanks
If you check the Javadoc, you'll see that when you call additionalInterceptors, you're not modifying the existing builder instance but instead getting a new builder with a slightly different configuration. When you then call restTemplateBuilder.build() in your test case, you're building a template that has the unmodified configuration.
The ideal way to test something like this is to do so directly on the interceptor instance itself, but if you're wanting to perform a functional test by calling through to a mock server, you should inject the completed RestTemplate instance into your test case.

Categories

Resources