Spring, webflux: The getRemoteAddress method of the ServerHttpRequest object returns null when request performed from WebTestClient - spring

I have a controller
#RestController
public class NameController {
#Autowired
private NameService nameService;
#GetMapping("/name")
public Mono<UploadParamsDto> getName(ServerHttpRequest request) {
return nameService.getNameByHost(request.getRemoteAddress().getHostName());
}
}
and i have a test method:
#ExtendWith(SpringExtension.class)
#WebFluxTest(NameControllerTest.class)
#ActiveProfiles("test")
class NameControllerTest {
#Autowired
private WebTestClient webClient;
#Test
void nameTest() {
webClient.get().uri("/name")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk();
}
}
When I run the test in order to check my getName method i got NPE because
request.getRemoteAddress() returns null, could you please tell me how to mock ServerHttpRequest? (I know that there is MockServerHttpRequest, but I couldn't managed with it)
My purpose is make request.getRemoteAddress().getHostName() return mock value.

Thanks to everyone.
Works in next way:
#ExtendWith(SpringExtension.class)
#WebFluxTest(NameControllerTest.class)
#ActiveProfiles("test")
class NameControllerTest {
#Autowired
private ApplicationContext context;
#Test
void nameTest() {
WebTestClient webClient = WebTestClient
.bindToApplicationContext(context)
.webFilter(new SetRemoteAddressWebFilter("127.0.0.1"))
.configureClient()
.build();
webClient.get().uri("/name")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk();
}
}
Where SetRemoteAddressWebFilter is WebFilter:
public class SetRemoteAddressWebFilter implements WebFilter {
private String host;
public SetRemoteAddressWebFilter(String host) {
this.host = host;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(decorate(exchange));
}
private ServerWebExchange decorate(ServerWebExchange exchange) {
final ServerHttpRequest decorated = new ServerHttpRequestDecorator(exchange.getRequest()) {
#Override
public InetSocketAddress getRemoteAddress() {
return new InetSocketAddress(host, 80);
}
};
return new ServerWebExchangeDecorator(exchange) {
#Override
public ServerHttpRequest getRequest() {
return decorated;
}
};
}
}

Running a test with #WebFluxTest doesn't involve a real server, you've figured that out.
But getting a NullPointerException doesn't feel right still - could you create an issue on https://jira.spring.io about that? I don't think you should have to work around this, but Spring Framework should probably provide some infrastructure to "mock" that information.

Related

Using Spring Boot WebClient to call a dummy api to postman

I am missing something here. I am attempting to pull information using Spring Boot WebClient from a Dummy Api that's an Http request. I am not getting any info pulled when I go into postman.
Thanks for any insight you can give me. I am still very new to coding and self-taught.
Here's my employee controller:
#Autowired
WebClientApp webClientApp;
#GetMapping("/consume")
public String getEmployee(Model model) {
model.addAttribute("listEmployees", empServiceImpl.getAllEmployees());
model.addAttribute("listemps", webClientApp.webClientBuilder());
return "index";
}
Web Client
private WebClient webClient;
public void SimpleWebClient(WebClient webClient) {
this.webClient = webClient;
}
public Flux<Employee> webClientBuilder() {
return this.webClient
//this.webClientBuilder = webClientBuilder.baseUrl(DummyEmployee)
.get()
.uri("api/v1/employees")
.retrieve()
.bodyToFlux(Employee.class);
}
Employee
#Data
#ToString
//#AllArgsConstructor
//#NoArgsConstructor
#JsonRootName(value = "data")
public class Employee {
#JsonProperty("id")
public int employeeID;
#JsonProperty("employee_name")
public String employeeName;
#JsonProperty("employee_salary")
public String employeeSalary;
#JsonProperty("employee_age")
public int employeeAge;
#JsonProperty("employee_image")
public Blob employeeImage;
}
Service
#Repository
#ComponentScan(basePackages = {"com.example.app.repository"})
#Service
public class ServiceImpl implements EmpService{
#Autowired
private EmployeeRepository employeeRepo;
#SuppressWarnings("unchecked")
public List<Employee> getAllEmployees() {
return (List<Employee>) employeeRepo.findAll();
}
}
Service
#Service
public interface EmpService {
static List<Employee> getAllEmployees() {
// TODO Auto-generated method stub
return null;
}
}
Main
public static void main(String[] args) {
SpringApplication.run(RestWebsiteDataProjectApplication.class, args);
}
#Bean
public WebClient webClientFromScratch() {
return WebClient.builder()
.baseUrl("https://dummy.restapiexample.com/")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
Flux only emits its content when it is subscribed. You are not subscribing to the Flux returned by the webClientBuilder() method.
You shouldn't really do this, but try adding .block() to your Controller as follows:
#Autowired
WebClientApp webClientApp;
#GetMapping("/consume")
public String getEmployee(Model model) {
model.addAttribute("listEmployees", empServiceImpl.getAllEmployees());
model.addAttribute("listemps", webClientApp.webClientBuilder().block());
return "index";
}
If this works, please consider reworking your code because while working with Spring WebFlux (reactive programming) you should always deal with Mono and Flux so that you can take full advantage of the reactive stack.

GET turning into POST with Spring Feign

I was facing an issue that my GET requests were being changed to POST due the RequestHeader and PathVariable that were being interpreted as body of the request in Feign Client.
Interceptor
public class OpenFeignConfiguration implements RequestInterceptor {
#Value("${key:}")
private String key;
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
#Override
public void apply(RequestTemplate template) {
template.header("key", key);
}
}
And the Feign Client
#FeignClient(name = "feignClient", url = "${client.url}", configuration = OpenFeignConfiguration.class)
public interface FeignClient {
#GetMapping(value = "/path/?test=({var1} and {var2})")
public Object test(String body, #PathVariable("var1") String var1, #PathVariable("var2") String var2);
}
The solution that I found is that you have to change Springs Feign contract to be Feign one so:
public class OpenFeignConfiguration implements RequestInterceptor {
#Value("${key:}")
private String key;
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
#Bean
public Contract feignContract() {
return new Contract.Default();
}
#Override
public void apply(RequestTemplate template) {
template.header("key", key);
}
}
And the client now must use the Feign annotation:
#FeignClient(name = "feignClient", url = "${client.url}", configuration = OpenFeignConfiguration.class)
public interface FeignClient {
#RequestLine("GET /path/?test=({var1} and {var2})")
public Object test(#Param("var1") String originator, #Param("var2") String receiver);
}
Hope that helps anyone having same issue that I had.

Spring boot - integration testing - WebTestClient & HttpServletRequest

I'm having difficulties figuring this out.
I can mock almost everything but for some reason the HttpServletRequest is mocked but not injected into the #ControllerAdvice #ExceptionHandler method.
Any ideas? Thank you for your help in advance!
STR Repo with minimal plug and play test suite / code
https://github.com/krodyrobi/spring-integration-test-str
#Component
public class Config {
private final String url = "https://httpstat.us/";
public String getUrl() {
return url;
}
}
#RestControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(WebClientResponseException.class)
public ResponseEntity<String> handleException(HttpServletRequest request, WebClientResponseException ex) {
return new ResponseEntity<>(request.getRequestURL() + " " + ex.getResponseBodyAsString(), ex.getStatusCode());
}
}
#RestController
public class SomeController {
private final Config config;
#Autowired
public SomeController(Config config) {
this.config = config;
}
#GetMapping("/test")
public Mono<String> test() {
return WebClient
.create(config.getUrl())
.get()
.uri("/200")
.retrieve()
.bodyToMono(String.class);
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {
SomeController.class,
GlobalExceptionHandler.class,
})
public class SomeControllerTest {
private final static String baseUrl = "http://localhost:9999/";
public #Rule WireMockRule wireMockRule = new WireMockRule(9999);
private #MockBean Config config;
private #MockBean HttpServletRequest request;
private WebTestClient webClient;
private #Autowired SomeController controller;
private #Autowired GlobalExceptionHandler exceptionHandler;
#Before
public void setUp() {
webClient = WebTestClient
.bindToController(controller)
.controllerAdvice(exceptionHandler)
.build();
when(config.getUrl()).thenReturn(baseUrl);
}
#Test
public void test_works() {
wireMockRule
.stubFor(get(urlEqualTo("/200"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "text/plain")
.withBody("200 MOCK")));
webClient
.get()
.uri("/test")
.exchange()
.expectStatus()
.isOk()
.expectBody(String.class)
.isEqualTo("200 MOCK");
wireMockRule.verify(getRequestedFor(urlMatching("/200")));
}
#Test
public void test_fails() {
// java.lang.IllegalStateException: No suitable resolver for argument 0
// of type 'javax.servlet.http.HttpServletRequest' on public
// org.springframework.http.ResponseEntity<java.lang.String>
// com.example.demo.GlobalExceptionHandler.handleException(
// javax.servlet.http.HttpServletRequest,
// ...client.WebClientResponseException
// )
wireMockRule
.stubFor(get(urlEqualTo("/200"))
.willReturn(aResponse()
.withStatus(404)
.withHeader("Content-Type", "text/plain")
.withBody("404 MOCK")));
webClient
.get()
.uri("/test")
.exchange()
.expectStatus()
.isNotFound()
.expectBody(String.class)
.isEqualTo("Http://localhost:8080/test 404 MOCK");
wireMockRule.verify(getRequestedFor(urlMatching("/200")));
}
}
Use below instead of HttpServletRequest
import org.springframework.http.server.reactive.ServerHttpRequest;
ServerHttpRequest request

Enabling Cookies and Redirects in TestRestTemplate

How to I add TestRestTemplate.HttpClientOption.ENABLE_REDIRECTS and TestRestTemplate.HttpClientOption.ENABLE_COOKIES to spring-boots TestRestTemplate?
I am using spring-boot so have a TestRestTemplate configured for me automatically.
I can customise this bean before creation using RestTemplateBuilder. The problem is I can't see how to add these options:
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.errorHandler(new ResponseErrorHandler() {
...
});
}
The documentation has some constructors that accept these options but the problem is the bean has already been created for me.
You can create a TestRestTemplate and present it to Spring by using the #Bean annotation.
For example:
#Bean
#Primary
public TestRestTemplate testRestTemplate() {
RestTemplate restTemplate = new RestTemplateBuilder()
.errorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
}
}).build();
return new TestRestTemplate(restTemplate, user, password, TestRestTemplate.HttpClientOption.ENABLE_REDIRECTS, TestRestTemplate.HttpClientOption.ENABLE_COOKIES);
}
Or, if you do not need to customise the RestTemplate then use the following constructor (which internally instances a RestTemplate for you):
#Bean
#Primary
public TestRestTemplate testRestTemplate() {
return new TestRestTemplate(TestRestTemplate.HttpClientOption.ENABLE_REDIRECTS, TestRestTemplate.HttpClientOption.ENABLE_COOKIES);
}
Update 1 to address this comment:
when I run my tests, I now get the following error org.apache.http.ProtocolException: Target host is not specified
The TestRestTemplate provided by Spring is configured to resolve paths relative to http://localhost:${local.server.port}. So, when you replace the Spring provided instance with your own instance you'll either have to provide the full address (including host and port) or configure your own TestRestTemplate with a LocalHostUriTemplateHandler (you can see this code in org.springframework.boot.test.context.SpringBootTestContextCustomizer.TestRestTemplateFactory). Here's an example of the latter approach:
#Bean
#Primary
public TestRestTemplate testRestTemplate(ApplicationContext applicationContext) {
RestTemplate restTemplate = new RestTemplateBuilder()
.errorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
}
}).build();
TestRestTemplate testRestTemplate =
new TestRestTemplate(restTemplate, user, password, TestRestTemplate.HttpClientOption
.ENABLE_REDIRECTS, TestRestTemplate.HttpClientOption.ENABLE_COOKIES);
// let this testRestTemplate resolve paths relative to http://localhost:${local.server.port}
LocalHostUriTemplateHandler handler =
new LocalHostUriTemplateHandler(applicationContext.getEnvironment(), "http");
testRestTemplate.setUriTemplateHandler(handler);
return testRestTemplate;
}
With this bean configuration the following test case uses the customised TestRestTemplate and successfully invokes the Spring Boot app on localhost:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RestTemplateTest {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void test() {
ResponseEntity<String> forEntity = this.restTemplate.getForEntity("/some/endpoint", String.class);
System.out.println(forEntity.getBody());
}
}

Testing Spring MVC #ExceptionHandler method with Spring MVC Test

I have the following simple controller to catch any unexpected exceptions:
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(Throwable.class)
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public ResponseEntity handleException(Throwable ex) {
return ResponseEntityFactory.internalServerErrorResponse("Unexpected error has occurred.", ex);
}
}
I'm trying to write an integration test using Spring MVC Test framework. This is what I have so far:
#RunWith(MockitoJUnitRunner.class)
public class ExceptionControllerTest {
private MockMvc mockMvc;
#Mock
private StatusController statusController;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new ExceptionController(), statusController).build();
}
#Test
public void checkUnexpectedExceptionsAreCaughtAndStatusCode500IsReturnedInResponse() throws Exception {
when(statusController.checkHealth()).thenThrow(new RuntimeException("Unexpected Exception"));
mockMvc.perform(get("/api/status"))
.andDo(print())
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.error").value("Unexpected Exception"));
}
}
I register the ExceptionController and a mock StatusController in the Spring MVC infrastructure.
In the test method I setup an expectation to throw an exception from the StatusController.
The exception is being thrown, but the ExceptionController isn't dealing with it.
I want to be able to test that the ExceptionController gets exceptions and returns an appropriate response.
Any thoughts on why this doesn't work and how I should do this kind of test?
Thanks.
I just had the same issue and the following works for me:
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(statusController)
.setControllerAdvice(new ExceptionController())
.build();
}
This code will add ability to use your exceptions controlled advice.
#Before
public void setup() {
this.mockMvc = standaloneSetup(commandsController)
.setHandlerExceptionResolvers(withExceptionControllerAdvice())
.setMessageConverters(new MappingJackson2HttpMessageConverter()).build();
}
private ExceptionHandlerExceptionResolver withExceptionControllerAdvice() {
final ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
#Override
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(final HandlerMethod handlerMethod,
final Exception exception) {
Method method = new ExceptionHandlerMethodResolver(ExceptionController.class).resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(new ExceptionController(), method);
}
return super.getExceptionHandlerMethod(handlerMethod, exception);
}
};
exceptionResolver.afterPropertiesSet();
return exceptionResolver;
}
Since you are using stand alone setup test you need to provide exception handler manually.
mockMvc= MockMvcBuilders.standaloneSetup(adminCategoryController).setSingleView(view)
.setHandlerExceptionResolvers(getSimpleMappingExceptionResolver()).build();
I had same problem a few days back, you can see my problem and solution answered by myself here Spring MVC Controller Exception Test
Hoping my answer help you out
Use Spring MockMVC to emulate a servletContainer to a point where you can incorporate any request filtering or exception handling tests in your unit tests suite.
You can configure this setup with the following approach:
Given a custom RecordNotFound exception...
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Record not found") //
public class RecordNotFoundException extends RuntimeException {
private static final long serialVersionUID = 8857378116992711720L;
public RecordNotFoundException() {
super();
}
public RecordNotFoundException(String message) {
super(message);
}
}
... and a RecordNotFoundExceptionHandler
#Slf4j
#ControllerAdvice
public class BusinessExceptionHandler {
#ExceptionHandler(value = RecordNotFoundException.class)
public ResponseEntity<String> handleRecordNotFoundException(
RecordNotFoundException e,
WebRequest request) {
//Logs
LogError logging = new LogError("RecordNotFoundException",
HttpStatus.NOT_FOUND,
request.getDescription(true));
log.info(logging.toJson());
//Http error message
HttpErrorResponse response = new HttpErrorResponse(logging.getStatus(), e.getMessage());
return new ResponseEntity<>(response.toJson(),
HeaderFactory.getErrorHeaders(),
response.getStatus());
}
...
}
Configure a tailored test context: set a #ContextConfiguration to specify the classes you need for your test. Set Mockito MockMvc as a servlet container emulator and set your tests fixture and dependencies.
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {
WebConfig.class,
HeaderFactory.class,
})
#Slf4j
public class OrganisationCtrlTest {
private MockMvc mvc;
private Organisation coorg;
#MockBean
private OrganisationSvc service;
#InjectMocks
private OrganisationCtrl controller = new OrganisationCtrl();
//Constructor
public OrganisationCtrlTest() {
}
....
Configure a mock MVC "servlet emulator": register handler beans in the context and build the mockMvc emulator (Note: there are two possible configuration: standaloneSetup or webAppContextSetup; refer to the documentation). The builder rightfully implements the Builder pattern so you can chain configuration commands for exception resolvers and handlers before calling build().
#Before
public void setUp() {
final StaticApplicationContext appContext = new StaticApplicationContext();
appContext.registerBeanDefinition("BusinessExceptionHandler",
new RootBeanDefinition(BusinessExceptionHandler.class, null, null));
//InternalExceptionHandler extends ResponseEntityExceptionHandler to //handle Spring internally throwned exception
appContext.registerBeanDefinition("InternalExceptionHandler",
new RootBeanDefinition(InternalExceptionHandler.class, null,
null));
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.setHandlerExceptionResolvers(getExceptionResolver(appContext))
.build();
coorg = OrganisationFixture.getFixture("orgID", "name", "webSiteUrl");
}
....
Get the exception resolver
private ExceptionHandlerExceptionResolver getExceptionResolver(
StaticApplicationContext context) {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.getMessageConverters().add(
new MappingJackson2HttpMessageConverter());
resolver.setApplicationContext(context);
resolver.afterPropertiesSet();
return resolver;
}
Run your tests
#Test
public void testGetSingleOrganisationRecordAnd404() throws Exception {
System.out.println("testGetSingleOrganisationRecordAndSuccess");
String request = "/orgs/{id}";
log.info("Request URL: " + request);
when(service.getOrganisation(anyString())).
thenReturn(coorg);
this.mvc.perform(get(request)
.accept("application/json")
.andExpect(content().contentType(
.APPLICATION_JSON))
.andExpect(status().notFound())
.andDo(print());
}
....
}
Hope this helps.
Jake.
Try it;
#RunWith(value = SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { MVCConfig.class, CoreConfig.class,
PopulaterConfiguration.class })
public class ExceptionControllerTest {
private MockMvc mockMvc;
#Mock
private StatusController statusController;
#Autowired
private WebApplicationContext wac;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void checkUnexpectedExceptionsAreCaughtAndStatusCode500IsReturnedInResponse() throws Exception {
when(statusController.checkHealth()).thenThrow(new RuntimeException("Unexpected Exception"));
mockMvc.perform(get("/api/status"))
.andDo(print())
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.error").value("Unexpected Exception"));
}
}
This is better:
((HandlerExceptionResolverComposite) wac.getBean("handlerExceptionResolver")).getExceptionResolvers().get(0)
And do not forget to scan for #ControllerAdvice beans in your #Configuration class:
#ComponentScan(basePackages = {"com.company.exception"})
...tested on Spring 4.0.2.RELEASE

Resources