Spring boot - integration testing - WebTestClient & HttpServletRequest - spring

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

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.

Using WebClient in a controller tested with #WebFluxTests throws java.lang.IllegalArgumentException: URI is not absolute

I have a #RestController that uses WebClient in one of its endpoints to invoke another endpoint from the same controller:
#RestController
#RequestMapping("/api")
#RequiredArgsConstructor
public class FooRestController {
private final WebClient webClient;
#Value("${service.base-url}")
private String fooServiceBaseUrl;
#GetMapping(value = "/v1/foo", produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<Foo> getFoo() {
return webClient.get().uri(fooServiceBaseUrl + "/api/v1/fooAnother")
.retrieve()
.bodyToFlux(Foo.class);
}
#GetMapping(value = "/v1/fooAnother", produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<Foo> getFooAnother() {
return Flux.xxx
}
In my #WebFluxTests class I can test the fooAnother endpoint without any problem:
#ExtendWith(SpringExtension.class)
#Import({MyWebClientAutoConfiguration.class})
#WebFluxTest(FooRestController.class)
class FooRestControllerTest {
#Test
void shouldGetFooAnother() {
xxx
webTestClient.get()
.uri("/api/v1/fooAnother")
.exchange()
.expectStatus().isOk()
}
#Test
void shouldGetFoo() {
xxx
webTestClient.get()
.uri("/api/v1/fooAnother")
.exchange()
.expectStatus().isOk()
}
However when I test the /v1/foo endpoint (notice in my tests service.base-url=""), it fails calling webClient.get().uri(fooServiceBaseUrl + "/api/v1/fooAnother") having fooServiceBaseUrl + "/api/v1/fooAnother" = "/api/v1/fooAnother", complaining that it need an absolute URL: java.lang.IllegalArgumentException: URI is not absolute: /api/v1/fooAnother.
How could I fix this test?
You have to configure your WebClient using WebClient.Builder(). You could do this inside your FooRestController but I like to use Configuration that way if you have any further WebClient customizations, you could do in different class rather than in your controller class.
Configure WebClient:
#Configuration
public class WebClientConfig() {
#Value("${service.base-url}")
private String fooServiceBaseUrl;
#Bean
public WebClient webClient(WebClient.Builder builder) {
return builder
.baseUrl(fooServiceBaseUrl)
.build();
}
}
If you decide to go ahead with configuring your webClient in your FooRestController, you have to refactor as below. You don't need above configuration.
If this doesn't solve your issue, you might have some sort of mismatch between application.yml file and the value that your are trying to inject in fooServiceBaseUrl.
#RestController
#RequestMapping("/api")
public class FooRestController() {
private final WebClient webClient;
#Value("${service.base-url}")
private String fooServiceBaseUrl;
public FooRestController(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder
.baseUrl(fooServiceBaseUrl)
.build();
}
#GetMapping(value = "/v1/foo", produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<Foo> getFoo() {
return webClient
.get()
.uri("/api/v1/fooAnother")
.retrieve()
.bodyToFlux(Foo.class);
}
#GetMapping(value = "/v1/fooAnother", produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<Foo> getFooAnother() {
return Flux.xxx
}
}

When using MockMvc to test the controller, a parameter passing error occurred

I am using MockMvc to test the controller. Regarding parameter import, I encountered a type mismatch problem. I tried all the json styles.But nothing works
This is my controller class::
package app.dnatask.controller;
import ......;
#Slf4j
#RestController
#RequestMapping(value = "/API/scanresultconfigure")
public class ScanResultConfigureController extends BaseController {
#RequestMapping(value = "/queryScanResultList/{taskId}/{externalname}", method = RequestMethod.POST)
public IBaseResult queryscanResultList(final HttpServletRequest request, #PathVariable final String taskId, #PathVariable final String externalname, #RequestBody Map map) throws Exception {
return runController(new IControllRunner() {
public void run(IOutResult or, CheckResult cr) throws Exception {
......
}
}
}
}
This is my test class::
package app.dnatask.controller;
import ......
#WebAppConfiguration
#ContextConfiguration(classes = {ScanResultConfigureController.class})
#ComponentScan(
includeFilters = {
#ComponentScan.Filter(type = FilterType.CUSTOM,
value = {ScanResultConfigureController.class})
},
useDefaultFilters = false,
lazyInit = true
)
public class ScanResultConfigureControllerTest extends AbstractTestNGSpringContextTests {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#BeforeMethod
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).dispatchOptions(true).build();
System.out.println("UT starting.............");
}
#AfterMethod
public void am() {
System.out.println("UT ending.............");
}
#Test
public void testQueryscanResultList() throws Exception {
Map<String, String> testMap = new HashMap<>();
testMap.put("key1", "value1");
testMap.put("key2", "value2");
String requestJson = JSONObject.toJSONString(testMap);
mockMvc.perform(
post("/API/scanresultconfigure/queryScanResultList/001/abc")
.contentType(MediaType.APPLICATION_JSON)
.param("map", requestJson)
)
.andExpect(status().isOk())
.andDo(print());
}
}
Error message::
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json' not supported
java.lang.AssertionError: Status expected:<200> but was:<415>
This is a project implemented by springmvc framework, I use TestNG for unit testing.
Regarding my problem, the solution is as follows::
MvcResult mvcResult = mockMvc.perform(
post("/API/scanresultconfigure/queryScanResultList/{taskId}/{externalname}", "123", "abc")
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson)
)
.andExpect(status().isOk())
.andDo(print())
.andReturn();

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

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.

Injected mocked Spring WebServiceTemplate to return predefined value

Our web service updated on their end. I updated our client code using Spring web service.
Problem is unit test failed at return since injected mocked WebServiceTemplate returns null.
My question is "Is there a way I can make the return some predefined value?"
#Configuration
public class TestConfig {
#Bean
public WebServiceTemplate webServiceTemplate() {
WebServiceTemplate webServiceTemplate = mock(WebServiceTemplate.class);
return webServiceTemplate;
}
#Bean
public TheServiceClient client() {
return new TheServiceClient();
}
}
public class TheServiceClient {
#Autowired
private WebServiceTemplate webServiceTemplate;
public TheResponse getResponse(TheRequest request) {
// logic handles the request need to be tested
JAXBElement<?> element = (JAXBElement<?>) webServiceTemplate.marshalSendAndReceive(request);
return element.getResponse();
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestConfig.class)
public class IdalClientTest {
#Autowired
private TheServiceClient client;
#Test
public void testGetResponse() {
TheRequest request = new TheRequest();
request.setters();
TheResponse response = client.getResponse(request);
assertThat(response.getSucess()).isTrue();
}
}
Because you are not injecting the mocked WebServiceTemplate to TheServiceClient.
You should do like this
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestConfig.class)
public class IdalClientTest {
#InjectMocks
private TheServiceClient client;
#Mock
WebServiceTemplate webServiceTemplate;
#Mock
JAXBElement jaxBElement;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(webServiceTemplate.marshalSendAndReceive(any(TheRequest.class))).thenReturn(jaxBElement);
// You can create a TheResponse object with success = true;
when(jaxBElement.getResponse()).thenReturn(dummyTheResponseObject);
}
#Test
public void testGetResponse() {
TheRequest request = new TheRequest();
request.setters();
TheResponse response = client.getResponse(request);
assertThat(response.getSucess()).isTrue();
}
}
You don't need that Configuration class.
Ideal way to do it to use Constructor Injection instead of Field Injection. Like this
public class TheServiceClient {
private final WebServiceTemplate webServiceTemplate;
#Autowired
public TheServiceClient(final WebServiceTemplate webServiceTemplate) {
this.webServiceTemplate = webServiceTemplate;
}
.......
}
Then in your test class instead of InjectMocks you can do like this
private TheServiceClient client;
#Mock
WebServiceTemplate webServiceTemplate;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
client = new TheServiceClient(webServiceTemplate);
.............
}
................

Resources