Get request attribute with Spring webflux and WebTestClient - java

I'm not able to bind an attribute that I'm setting from a WebTestClient into a RestController when using Spring WebFlux.
I tried the two ways I could think of.
First using the #RequestAttribute annotation and I got:
Failed to handle request [GET /attributes/annotation]: Response status 400 with reason "Missing request attribute 'attribute' of type String"
Then I tried with the ServerWebExchange and was null.
This is my controller:
#RestController
#RequestMapping("/attributes")
public class MyController {
#GetMapping("/annotation")
public Mono<String> getUsingAnnotation(#RequestAttribute("attribute") String attribute) {
return Mono.just(attribute);
}
#GetMapping("/exchange")
public Mono<String> getUsingExchange(ServerWebExchange exchange) {
return Mono.just(exchange.getRequiredAttribute("attribute"));
}
}
And this is my failing test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyControllerTest {
#Autowired
ApplicationContext context;
WebTestClient webClient;
#Before
public void setup() {
webClient = WebTestClient.bindToApplicationContext(context)
.configureClient()
.build();
}
#Test
public void testGetAttributeUsingAnnotation() {
webClient.get()
.uri("/attributes/annotation")
.attribute("attribute", "value")
.exchange()
.expectStatus()
.isOk();
}
#Test
public void testGetAttributeUsingExchange() {
webClient.get()
.uri("/attributes/exchange")
.attribute("attribute", "value")
.exchange()
.expectStatus()
.isOk();
}
}
In my real application I have a SecurityContextRepository that sets some attributes from a (decoded) header value and I'd like to get those attributes.

I've run into the same issue with a test which previously used MockMvc and then had to be converted to use WebClient. Like #jcfandino I was expecting the .attribute() methods on the WebClient to work similar to MockMvc's requestAttribute().
I haven't found out how .attribute() is meant to be used but I've bypassed the entire problem by adding a custom test filter. I'm not sure if this approach is correct but since this question has been unanswered the approach below may be of help for people running into the same issue.
#WebFluxTest(controllers = SomeController.class)
#ComponentScan({ "com.path1", "com.path2" })
class SomeControllerTest {
// define a test filter emulating the server's filter (assuming there is one)
private class AttributeFilter implements WebFilter {
String attributeValue;
public AttributeFilter(String filterAttributeValue) {
attributeValue = filterAttributeValue;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// add the desired attributes
exchange.getAttributes().put(SomeController.ATTR_NAME, attributeValue);
return chain.filter(exchange);
}
}
// mock the service the controller is dependend on
#MockBean
AppService appService;
// define the test where the controller handles a get() operation
#Test
void testMethod() {
// mock the app service
when(appService.executeService(anyString(), anyString())).thenAnswer(input -> {
// ... return some dummy appData
});
var testClient= WebTestClient.bindToController(new SomeController(appService))
.webFilter(new SomeControllerTest.AttributeFilter("someValue"))
.build();
try {
var response = testClient
.get()
.uri("someroute")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody(AppData.class);
} catch (Exception e) {
fail("exception caught in testMethod", e);
}
}
}

Both on the server and client side, request attributes should be seen as Map-like data structures that can be used to transfer information within the client/server (for filters, codecs, etc).
That information is not sent over the network.
If you want to send that information from the client to the server, you should take a look at request params or the request body itself.

Related

Mock API calls During Unit Test cases Spring boot

I have two microservices Microservice A ( context path - /abc ) and microservice B (context path - /def )
Example URLs: test.domain.com/abc/endpoint1 ,test.domain.com/def/endpoint2
In one of the apis of Microservice A ( test.domain.com/abc/endpoint1) internally its making call to Microservice B (/def/endpoint2) -> the prefix for this internal call is generated as follows
(Extract the domain from the request and then append /def/endpoint2 to make a rest call the total url will become as (test.domain.com/def/endpoint2)
Problem : When we are writting unit test cases starting controller level we are using TestRestTemplate
For this testing we need to use http://localhost:portnumber/abc/endpoint1 to test ..
Now the url of the def service also will be derived as http://localhost:portnumber/def/endpoint2
How to mock this response ( Note: We cannot use mock server on same port, we will get port binding exception) . Is there any workaround for the same?
Is there any way to have gateway kind of setup while using TestRestTemplate to route http://localhost:portnumber/def/* calls to get response from mockserver and http://localhost:portnumber/abc/* to make the actual API Service under test?
You could use a ClientHttpRequestInterceptor for this and manipulate the actual URI to call if it matches the path of your second microservice.
This might be a naive protoypish implementation:
public class UrlRewriter implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
try {
if (httpRequest.getURI().toString().contains("/def/abc")) {
HttpRequest modifiedRequest = new MockClientHttpRequest(HttpMethod.GET, new URI("http://localhost:8888/def/abc"));
return clientHttpRequestExecution.execute(modifiedRequest, bytes);
} else {
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
} catch (URISyntaxException e) {
e.printStackTrace();
return null;
}
}
}
And then you can provide a custom bean of type RestTemplateBuilder for your test that is picked up by the TestRestTemplate:
#SpringBootTest(webEnvironment = RANDOM_PORT)
public class TestOne {
#TestConfiguration
static class TestConfig {
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder().interceptors(new UrlRewriter());
}
}
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void test() {
assertNotNull(testRestTemplate);
testRestTemplate.getForObject("/abc/endpoint1", String.class);
}
}

How to increase timeout AsyncRestTemplate class?

I have developed some async web services with spring framework and REST, I have consumed it from a client created with spring class AsyncRestTemplate. Class return an object ListenableFuture<ResponseEntity<T>> (with the method getForEntity), which brings the value returned by the web service (with the method .get():<T>) . It works fine, however when the web service takes a lot time the method isDone() of ListenableFuture class return a value true, even when the web service has not finished to work.
If I try to recover the web service response with the method get() in the client and It has late a lot of time, I always get the follows message:
"timestamp": "2018-05-29T22:42:26.978+0000",
"status": 500,
"error": "Internal Server Error",
"message": "java.util.concurrent.ExecutionException: org.springframework.web.client.HttpServerErrorException: 503 null",
"path": "/client/result"
Does someone knows how can i solve the problem?. I want the client shows me the web service response, even when the web service takes a lot time (I want to increase the timeout).
The server codes are the following:
Configuration Class:
#Configuration
#EnableAsync
public class ConfigurationClass {
#Bean
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
Controller class:
#RestController
#RequestMapping("/server")
public class ControllerClass {
#GetMapping("/start")
#Async
public CompletableFuture<String> callService() throws InterruptedException{
Thread.sleep(100000L);
return CompletableFuture.completedFuture("OK");
}
}
The client code (consumer) is the following:
#RestController
#RequestMapping("/client")
public class ControllerClass {
private ListenableFuture<ResponseEntity<String>> entity;
#GetMapping("/start")
#Async
public void callService() throws InterruptedException {
AsyncRestTemplate restTemplate = new AsyncRestTemplate();
entity = restTemplate.getForEntity("http://localhost:8080/server/start",
String.class);
}
#GetMapping("/state")
public boolean getState() {
try {
return entity.isDone();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#GetMapping("/result")
public ResponseEntity<String> getResult() {
try {
return entity.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
I tried to increase the property timeout in application.property file, but it did not work.
# SPRING MVC (WebMvcProperties)
spring.mvc.async.request-timeout= 500000 # Amount of time before asynchronous request handling times out.
Thanks for your help, Regards.
For a better maintenance, you can config a AsyncRestTemplate bean:
#Bean
public AsyncRestTemplate asyncRestTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setTaskExecutor(new SimpleAsyncTaskExecutor());
factory.setConnectTimeout(1000);//milliseconds
factory.setReadTimeout(2000);//milliseconds
return new AsyncRestTemplate(factory);
}
And then, autowired this bean:
#Autowired
private AsyncRestTemplate restTemplate;
After that, update your callService:
#GetMapping("/start")
public void callService() throws InterruptedException {
entity = restTemplate.getForEntity("http://localhost:8080/server/start",
String.class);
}
You can remove the #Async Annotation as the AsyncRestTemplate is Asynchronous.

SpringBootTest with MockMvcBuilders stand alone setup is not loading my ControllerAdvice despite setting it

I am creating my controller and controller advice like this:
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest
public class TestController {
private MockMvc mockMvc;
#Mock
private MyService myService;
#Autowired
#InjectMocks
private MyController myController;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
//Build the controller mock handler
mockMvc = MockMvcBuilders
.standaloneSetup(MyController.class)
.setControllerAdvice(new MyControllerAdvice())
//This also doesn't work
//.setHandlerExceptionResolvers(createExceptionResolver())
.build();
}
//This also did not work
private ExceptionHandlerExceptionResolver createExceptionResolver() {
ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
Method method = new ExceptionHandlerMethodResolver(MyControllerAdvice.class).resolveMethod(exception);
return new ServletInvocableHandlerMethod(new MyControllerAdvice(), method);
}
};
exceptionResolver.afterPropertiesSet();
return exceptionResolver;
}
/**
* Tests passing bad input to see if our exception handler is called.
*/
#Test
public void testBadRequest()
{
//Make a request object that has a bad input (e.g. bad date string)
MyRequest request = new MyRequest();
//Set the request values
request.setDate( "a" );
try
{
myController.getSomething( request );
}
catch (Exception e)
{
//It reaches here without ever reaching my controller advice in debugging
e.printStackTrace();
}
}
}
Controller advice:
#EnableWebMvc
#ControllerAdvice
#Component
public class MyControllerAdvice {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<String> handleException(HttpServletRequest request, Exception exception) throws Exception
{
//This is never called (I'm using a debugger and have a breakpoint here)
return new ResponseEntity<String>(
"test",
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
There are two issues in your example:
MockMvcBuilders#standaloneSetup() receives Controller objects as parameters, not the Class objects. So it should be:
mockMvc = MockMvcBuilders
.standaloneSetup(new MyController())
.setControllerAdvice(new MyControllerAdvice())
.build();
You are calling myController.getSomething( request ) directly, while you should use previously built mockMvc. Direct call is unadvised as it's not processed with TestDispatcherServlet. Here is a couple of examples for mockMvc requests:
GET
mockMvc.perform(get("/testSomething"))
.andExpect(status().is5xxServerError())
.andReturn();
POST
mockMvc.perform(post("/testSomething")
.contentType(MediaType.APPLICATION_JSON)
.content(json)) //it's JSON string
.andExpect(status().is5xxServerError())
.andReturn();

Testing REST endpoints with custom exception handling

I am working on a project with Spring microservices (modules) and I want to test my REST endpoint using MockMvc. My testing works fine for cases where the request is valid but it is not working when requesting a url that is invalid. By not working I mean my custom exception handler (#ControllerAdvice) does not get called, the exception gets thrown and the test fails.
My exception handler and testing class are implemented in different modules.
common-module (ExceptionHandler)
#ControllerAdvice
public class CoreExceptionHandler {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<ErrorMessageDTO> handleException(Exception ex, HttpServletRequest request) {
// Getting servlet request URL
String uri = request.getRequestURI();
HttpStatus a;
ErrorMessageDTO errorMessage;
if (ex instanceof CoreException) {
CoreException e = (CoreException) ex;
...
errorMessage = new ErrorMessageDTO(e, uri);
} else {
errorMessage = new ErrorMessageDTO(ex, uri);
...
}
return new ResponseEntity<ErrorMessageDTO>(errorMessage, a);
}
}
country-module
This is where my REST endpoint and Testing class are implemented. The common module dependency is included in this module's pom.xml and the packages are scanned through the main class.
CountryApplication.java
#EnableCaching
#EnableDiscoveryClient
#EnableAspectJAutoProxy
#SpringBootApplication(scanBasePackages = {
"com.something1.something2.something3.common.exception",
"com.something1.something2.something3.common.util.logged",
"com.something1.something2.something3.country"
})
public class CountryApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(CountryApplication.class, args);
}
...
}
CountryService.java
This is a method in my Service class.
#GetMapping("/{id:\\d+}")
public CountryDTO getCountryById(#PathVariable("id") Integer id) throws CoreException {
Country countryEntity = this.countryRepository.findOne(id);
// requesting for id that does not exist
if (countryEntity == null) {
throw new CoreException(CoreError.ENTITY_NOT_FOUND);
}
return this.countryMapper.daoToDto(countryEntity);
}
CountryServiceTest.java
#SpringBootTest
#AutoConfigureMockMvc
#AutoConfigureTestDatabase
#RunWith(SpringRunner.class)
public class CountryServiceTest {
...
#Autowired
private MockMvc mockMvc;
#Test
public void getByIdTest() throws Exception {
// Get by id exists
mockMvc.perform(get("/2"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andDo(print());
// Get by id not exists. NOT WORKING
mockMvc.perform(get("/100000"))
.andExpect(status().isNotFound())
.andExpect(content().contentType(contentType));
}
}
As I described above, the problem is that at the second request of the test method, the CoreExceptionHandler does not get called and the test fails throwing a:
NestedServletException: Request processing failed; nested exception is com.something1.something2.something3.common.exception.CoreException.
The dependency for the common module is well configured (at least when I am deploying in non-test mode) since I am using it for other things too, plus the ExceptionHandler gets called when I am not testing.
Another strange thing is that when I am deploying my Test, Spring Boot's logs show that the CoreExceptionHandler gets detected. This is the line. Detected #ExceptionHandler methods in coreExceptionHandler
There are two problems as explained below:
(1) ControllerAdvice not being set for MockMvc object in your CountryServiceTest class, which can be done as shown below:
MockMvc mockMvc = standaloneSetup(yourController)
.setHandlerExceptionResolvers(new CoreExceptionHandler())
.build();
(2) Because CoreException is wrapper by NestedServletException by the Spring Container, you need to use exception.getCause() to check your exception as shown below:
#ControllerAdvice
public class CoreExceptionHandler {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<ErrorMessageDTO> handleException(Exception ex,
HttpServletRequest request) {
// Getting servlet request URL
String uri = request.getRequestURI();
HttpStatus a;
ErrorMessageDTO errorMessage;
//check with exception cause
if (ex.getCause() instanceof CoreException) {
CoreException e = (CoreException) ex;
...
errorMessage = new ErrorMessageDTO(e, uri);
} else if (ex instanceof CoreException) {
//this block will be used only when direct CoreException triggered
CoreException e = (CoreException) ex;
...
errorMessage = new ErrorMessageDTO(e, uri);
} else {
errorMessage = new ErrorMessageDTO(ex, uri);
...
}
return new ResponseEntity<ErrorMessageDTO>(errorMessage, a);
}
}
Also, I suggest not to handle all exception types in a single generic method, which will be very hard to support/maintain, rather split your CoreExceptionHandler using multiple #ExceptionHandler methods/classes.

Spring boot - rest template and rest template builder

As I know the RestTemplateBuilder is some kind of factory for RestTemplate. I have a few questions about using it:
Very often in examples there is something like this in #Configuration class:
#Bean
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate();
...
return restClient;
}
Shouldn't RestTemplate be instantiated per #Service class ? If so, how to customize it ?
Spring reference says that RestTemplateBuilder should be customized via RestTemplateCustomizer. How to manage many URI's from many IP addresses with one builder ?
How to add BasicAuthentication globaly to all RestTemplates via RestTemplateBuilder, and is it a good practice?
Thanks for help.
UPDATE:
My application calls rest services from many servers at different IP's and urls - so logically for me is the situation when I have many RestTemplates.
I'm trying to have a factory (RestTemplateBuilder) per server - let's say servers A, B, C. I know how to add a basic authentication. But what for example when I want a basic authentication for server A but not for server B ?
I think about having one RestTemplateBuilder per server. I don't want to do this manually - I would prefer to use Spring mechanisms.
Any help ?
No, you don't need to, typically you will have on rest template instance, and you would pass different url, and request parameters accordingly every time.
String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, vars);
Foo foo = restTemplate.getForObject(fooResourceUrl + "/1", Foo.class);
A descriptive example from spring doc, you can add as many customizers to the builder
public class ProxyCustomizer implements RestTemplateCustomizer {
#Override
public void customize(RestTemplate restTemplate) {
HttpHost proxy = new HttpHost("proxy.example.com");
HttpClient httpClient = HttpClientBuilder.create()
.setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {
#Override
public HttpHost determineProxy(HttpHost target,
HttpRequest request, HttpContext context)
throws HttpException {
if (target.getHostName().equals("192.168.0.5")) {
return null;
}
return super.determineProxy(target, request, context);
}
}).build();
restTemplate.setRequestFactory(
new HttpComponentsClientHttpRequestFactory(httpClient));
}
}
Any RestTemplateCustomizer beans will be automatically added to the
auto-configured RestTemplateBuilder. Furthermore, a new
RestTemplateBuilder with additional customizers can be created by
calling additionalCustomizers(RestTemplateCustomizer…​)
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}
I've set up my config like this:
#Bean
public RestTemplateCustomizer restTemplateCustomizer() {
return restTemplate -> {
restTemplate.setRequestFactory(clientHttpRequestFactory());
};
}
#Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();
clientHttpRequestFactory.setConnectTimeout(connectionTimeoutMs);
clientHttpRequestFactory.setReadTimeout(connectionTimeoutMs);
clientHttpRequestFactory.setBufferRequestBody(false);
return clientHttpRequestFactory;
}
Whenever Spring injects a RestTemplateBuilder, it will configure it using this RestTemplateCustomizer to use the ClientHttpRequestFactory. You may need to do some different customizations, or perhaps none in which case don't declare the bean.
To add the authentication header, you will need to know the user name and password, which you probably won't know until run-time. So I've created an Authenticator bean:
#Component
public class Authenticator {
#Autowired
private RestTemplateBuilder restTemplateBuilder;
public void withAuthenticationHeader(String username, String password, Consumer<RestTemplate> doAuthenticated) {
RestTemplate restTemplate =
restTemplateBuilder
.basicAuthorization(username, password)
.build();
try {
doAuthenticated.accept(restTemplate);
} catch (HttpClientErrorException exception) {
// handle the exception
}
}
}
This allows me to handle authentication failures in a standard way for all requests, which is what I need in my application.
It is injected into other beans and used like so:
#Autowired
private Authenticator authenticator;
public void transmit() {
authenticator.withAuthenticationHeader(username, password, restTemplate ->
restTemplate.postForLocation(url, request));
}
So you'd use the Authenticator rather than using the RestTemple directly.
I couldn't find any standard patterns for this sort of thing, but this seems to work.

Categories

Resources