WebTestClient with MockWebServer hangs - spring-boot

I have a spring boot application that exposes a rest api using "spring-boot-starter-webflux".
My application calls other rest services when one of my endpoint is called.
I'm trying to make an integration test using WebTestClient to mimic the client calling me and MockWebServer to mimic the external rest service I call.
I'm also overriding a spring configuration to use a webclient that "points" to the MockWebServer in my application.
I see that, when I launch the test, my server starts, and the MockWebServer starts, but when I call my endpoint it seems that the MockWebServer is "stuck" and is not responding.
The test, moreover, ends with "java.lang.IllegalStateException: Timeout on blocking read for 5000 MILLISECONDS from the WebTestClient.
#SpringBootTest(webEnvironment = RANDOM_PORT,
properties = { "spring.main.allow-bean-definition-overriding=true" })
class ApplicationIT {
static MockWebServer remoteServer = new MockWebServer();
#BeforeAll
static void setUp() throws Throwable {
remoteServer.start();
}
#AfterAll
static void tearDown() throws Throwable {
remoteServer.shutdown();
}
#Test
void test(#Autowired WebTestClient client) {
remoteServer.enqueue(new MockResponse().setBody("..."));
client.get()
.uri("...")
.exchange()
.expectStatus()
.isOk();
}
#TestConfiguration
static class SpringConfiguration {
WebClient remoteClient() {
return WebClient.builder()
.baseUrl("http://" + remoteServer.getHostName() + ":" + remoteServer.getPort())
.build();
}
}
}

Are you sure that your integration tests uses the WebClient bean you specified inside your #TestConfiguration?
It seems there is an #Bean annotation missing:
#TestConfiguration
static class SpringConfiguration {
#Bean
WebClient remoteClient() {
return WebClient.builder()
.baseUrl("http://" + remoteServer.getHostName() + ":" + remoteServer.getPort())
.build();
}
}

Related

How to mock external rest services when writing integration test in spring boot

I have a controller from which gateway(Spring integration) is being called. Inside gateway I have several flows where I'm doing some outboundgateway calls. I've written my integration test as below -
#Tag("integrationtest")
#ExtendWith(SpringExtension.class)
#SpringBootTest(
classes = MyWebApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTest {
#LocalServerPort private int port;
TestRestTemplate testRestTemplate = new TestRestTemplate();
HttpHeaders headers = new HttpHeaders();
#Test
void testEntireApplication() {
HttpEntity<LoanProvisionRequest> entity =
new HttpEntity(TestHelper.generateValidLionRequest(), headers);
ResponseEntity<LoanProvisionResponse> response =
testRestTemplate.exchange(
createURLWithPort("/provision"), HttpMethod.POST, entity, LionResponse.class);
assertEquals(1, response.getBody().getASMCreditScoreResultCd());
}
private String createURLWithPort(String uri) {
return "http://localhost:" + port + "/lion-service/v1/decisions" + uri;
}
}
It's running the application and proceeding through from controller to the gateway and running the flows as expected. But for the outboundgateway calls it's failing by saying Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://someurl" because it's not able to access the url that's used in the outboundgateway. I want to stub/mock those url somehow. How do I do that?
I tried doing something below in the same class to mock the url -
MockRestServiceServer mockServer;
#BeforeEach
void setUp() throws JsonProcessingException {
RestTemplate restTemplate = new RestTemplate();
mockServer = MockRestServiceServer.bindTo(restTemplate).build();
DecisionResponse decisionResponse = new DecisionResponse();
creditDecisionResponse.setId("0013478");
creditDecisionResponse.setResponse(null);
creditDecisionResponse.setDescription("dummy Response");
mockServer
.expect(
requestTo(
"http://xyz-some-url:8080/some-other-service/v1/do-decisions/decision"))
.andExpect(method(HttpMethod.POST))
.andRespond(
withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(new ObjectMapper().writeValueAsString(decisionResponse )));
mockServer.verify();
}
But still the same error showing and somehow it's not getting called when it's hitting the outboundgateway call inside the gateway flows.
below is the controller code -
public ResponseEntity<LionResponse> getLionsNames(
#RequestBody final #Valid LionRequest req,
BindingResult bindingResult,
#RequestHeader HttpHeaders httpHeaders)
throws JsonProcessingException {
Long dbId = new SequenceGenerator().nextId();
lionsGateway.processLionRequest(
MessageBuilder.withPayload(req).build(),
dbId,
SourceSystem.ONE.getSourceSystemCode()));
below is the gateway -
#MessagingGateway
public interface LoansGateway {
#Gateway(requestChannel = "flow.input")
List<Object> processLoanRequest(
#Payload Message lionRequest,
#Header("dbID") Long dbID,
#Header("sourceSystemCode") String sourceSystemCode);
}
below is the SpringIntegrationConfiguration class -
#Bean
public IntegrationFlow flow() {
return flow ->
flow.handle(validatorService, "validateRequest")
.split()
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.scatterGather(
scatterer ->
scatterer
.applySequence(true)
.recipientFlow(savingLionRequestToTheDB())
.recipientFlow(callingANativeMethod())
.recipientFlow(callingAExternalService()),
gatherer -> gatherer.outputProcessor(prepareCDRequest()))
.gateway(getDecision(), f -> f.errorChannel("lionDecisionErrorChannel"))
.to(getDataResp());
}
public IntegrationFlow callingAExternalService() {
return flow ->
flow.handle(
Http.outboundGateway(externalServiceURL)
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class))
.logAndReply("Cd response");
}
.... same way I have other flows that are using outboundgateway but I've not wired the Restemplate instance anywhere.
So, you do in your mock server setup:
RestTemplate restTemplate = new RestTemplate();
mockServer = MockRestServiceServer.bindTo(restTemplate).build();
And that's it. The mocked RestTemplate instance is not used anywhere.
The HttpRequestExecutingMessageHandler has a configuration based on the RestTemplate:
/**
* Create a handler that will send requests to the provided URI using a provided RestTemplate.
* #param uri The URI.
* #param restTemplate The rest template.
*/
public HttpRequestExecutingMessageHandler(String uri, RestTemplate restTemplate) {
So, you just need to instrument exactly that RestTemplate which you provide for your HTTP outbound gateway.
Right now your mocking code is dead end.

Authorize OAuth2 request in #Async method

I've got a REST api secured with Spring Security and OAuth2 and JWT. This service is an OAuth client as well, as it needs to connect to other services (using client credentials grant).
Requests to other services, which are as well secured with OAuth is done using OpenFeign and here is the configuration for OAuth2.
#Slf4j
#Configuration
public class OAuth2OpenFeignConfig {
#Value("${client-name}")
private String clientName;
private final ClientRegistrationRepository clientRegistrationRepository;
public OAuth2OpenFeignConfig(ClientRegistrationRepository clientRegistrationRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
}
#Bean
public RequestInterceptor requestInterceptor(OAuth2AuthorizedClientManager authorizedClientManager) {
var clientRegistration = clientRegistrationRepository.findByRegistrationId(clientName);
var clientCredentialsFeignManager = new OAuthClientCredentialsFeignManager();
return requestTemplate -> requestTemplate.header("Authorization", "Bearer " + clientCredentialsFeignManager.getAccessToken(authorizedClientManager, clientRegistration));
}
static class OAuthClientCredentialsFeignManager {
public String getAccessToken(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) {
try {
var oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(SecurityContextHolder.getContext().getAuthentication())
.build();
var client = manager.authorize(oAuth2AuthorizeRequest);
if (isNull(client)) {
throw new IllegalStateException("client credentials flow on " + clientRegistration.getRegistrationId() + " failed, client is null");
}
return client.getAccessToken().getTokenValue();
} catch (Exception ex) {
log.error("client credentials error " + ex.getMessage());
}
return null;
}
}
}
All this works as expected in a sync configuration: Feign configuration requests and injeect a JWT in the header. Problems started when tried to rewrite the calls using async (either Runnable or #Async have the same result).
As explained here and here the Security Context is not propagated by default to other threads, for which I need to manually configure
#Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(1000);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setThreadNamePrefix("Async-");
executor.initialize(); // this is important, otherwise an error is thrown
return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}
Now I can see that the context gets propagated to the async thread. However, when trying to authenticate the OpenFeign client, an exception is thrown in
var client = manager.authorize(oAuth2AuthorizeRequest)
as internally it calls
HttpServletRequest servletRequest = getHttpServletRequestOrDefault(authorizeRequest.getAttributes());
but the servletRequest is null and the credentials are not set in the request.
Any idea on how to pass the HttpContext to a thread? Should I be concerned by https://docs.spring.io/spring-framework/docs/5.0.2.RELEASE/kdoc-api/spring-framework/org.springframework.web.filter/-request-context-filter/set-thread-context-inheritable.html ?
Please have a look at AuthorizedClientServiceOAuth2AuthorizedClientManager. It is capable of working outside of servlet context.

How Can I Mock A Specific URL Path?

I am using okhttp to mock my http responses during my tests.
//Create a mock server
mockWebServer.start(8080)
mockWebServer.enqueue(MockResponse().setBody("").setResponseCode(HttpURLConnection.HTTP_OK))
However, this responds to every path as OK.
How do I mock a specific url instead of all of them?
Using Dispatcher
Dispatcher dispatcher = new Dispatcher() {
#Override
public MockResponse dispatch(RecordedRequest request) {
switch (request.getPath()) {
case "/get":
return new MockResponse().setResponseCode(200).setBody("test");
}
return new MockResponse().setResponseCode(404);
}
};
mockBackEnd.setDispatcher(dispatcher);
Above can be written inside your test method. You can have a bunch of URLs conditions there.
The mockwebserver can be started once like:
public static MockWebServer mockBackEnd;
#BeforeAll
static void setUp() throws IOException {
mockBackEnd = new MockWebServer();
mockBackEnd.start();
}
#AfterAll
static void tearDown() throws IOException {
mockBackEnd.shutdown();
}
Use #DynamicPropertySource to change any property with mockserver host/port.

Hystrix and Spring #Async in combination

I'm using Hystrix library for the Spring Boot project (spring-cloud-starter-hystrix). I have a #Service class annotated with #HystrixCommand and it works as expected.
But, when I add the method annotated with #Async in that same service class then the Hystrix doesn't work, and fallback method is never called. What could cause this problem and how to fix it?
This is the Application class:
#EnableCircuitBreaker
#EnableHystrixDashboard
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
This is the service class:
#Service
public class TemplateService {
#HystrixCommand(
fallbackMethod = "getGreetingFallback",
commandProperties = {#HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")}
)
public String getGreeting() {
URI uri = URI.create("http://localhost:8090/greeting");
ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, null, String.class);
if (response.getStatusCode().equals(HttpStatus.OK)) {
return response.getBody();
} else {
return null;
}
}
public String getGreetingFallback(Throwable e) {
return null;
}
#Async
public void async(String message) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
logger.info(MessageFormat.format("Received async message {0}", message));
}
}
#EnableAsync annotation is placed in a different class annotated with #Configuration, where I set some other Thread Executor options from properties file.
Given the code for TemplateService (which doesn't implement interface) and assuming the defaults on #EnableAsync it is safe to concur that CGLIB proxies are created by spring.
Thus the #HystrixCommand annotation on getGreeting() isn't inherited by the service proxy class; which explains the reported behavior.
To get past this error keep the #HystrixCommand and #Async method separated in different service because enabling JDK proxies will also not help and I am not sure about AspectJ mode.
Refer this for further information on Spring proxy mechanism.

How to Create or configure Rest Template using #Bean in Spring Boot

I want to define RestTemplate as an application bean using #Bean annotation in my configuration class in a spring boot application.
I am calling 4 rest services in different places in my application flow. Currently I am creating RestTemplate every time every request. Is there a way I can define that as application bean using #Bean and inject that using #Autowired?
Main reason for this question is I can able to define RestTemplate using #Bean but when I inject it with #Autowired I am loosing all defined interceptors (Interceptors are not getting called.)
Configuration Class
#Bean(name = "appRestClient")
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new RestServiceLoggingInterceptor());
restClient.setInterceptors(interceptors);
return restClient;
}
Service Class
public class MyServiceClass {
#Autowired
private RestTemplate appRestClient;
public String callRestService() {
// create uri, method response objects
String restResp = appRestClient.getForObject(uri, method, response);
// do something with the restResp
// return String
}
}
It seems my Interceptors are not getting called at all with this configuration. But RestTemplate is able to make a call to the REST service and get a response.
Answer for Spring boot 2.*.* version.
I am using Spring boot 2.1.2.RELEASE and I also added RestTemplate in my project in a class where mail method exists.
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.setConnectTimeout(Duration.ofMillis(300000))
.setReadTimeout(Duration.ofMillis(300000)).build();
}
and Used in my service or other classes like this
#Autowired
RestTemplate res;
and in methods
HttpEntity<String> entity = new HttpEntity<>(str, headers);
return res.exchange(url, HttpMethod.POST, entity, Object.class);
Judging form the name of the interceptor, I'm guessing you're doing some logging in it? You could of missed logging level configuration. I created a small application to check weather your configuration works, using 1.3.6.RELEASE version.
In this class I define the RestTemplate bean and the interceptor with logging.
package com.example;
// imports...
#SpringBootApplication
public class TestApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(TestApplication.class);
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean(name = "appRestClient")
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
// Add one interceptor like in your example, except using anonymous class.
restClient.setInterceptors(Collections.singletonList((request, body, execution) -> {
LOGGER.debug("Intercepting...");
return execution.execute(request, body);
}));
return restClient;
}
}
For logging to work, I also have to set the correct debug level in application.properties.
logging.level.com.example=DEBUG
Then I create a service where I inject this RestTemplate.
#Service
public class SomeService {
private final RestTemplate appRestClient;
#Autowired
public SomeService(#Qualifier("appRestClient") RestTemplate appRestClient) {
this.appRestClient = appRestClient;
}
public String callRestService() {
return appRestClient.getForObject("http://localhost:8080", String.class);
}
}
And also an endpoint to test this out.
#RestController
public class SomeController {
private final SomeService service;
#Autowired
public SomeController(SomeService service) {
this.service = service;
}
#RequestMapping(value = "/", method = RequestMethod.GET)
public String testEndpoint() {
return "hello!";
}
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String test() {
return service.callRestService();
}
}
By performing a GET request to http://localhost:8080/test I should expect to get the String hello! getting printed (the service makes a call to http://localhost:8080 which returns hello! and sends this back to me). The interceptor with logger also prints out Intercepting... in the console.
Edd's solution won't work if you're using Spring Boot 1.4.0 or later. You will have to use RestTemplateBuilder to get this working. Here is the example
#Bean(name="simpleRestTemplate")
#Primary
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
RestTemplate template = restTemplateBuilder.requestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))
.interceptors(logRestRequestInterceptor) //This is your custom interceptor bean
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
return template;
}
Now you can autowire the bean into your service class
#Autowired
#Qualifier("simpleRestTemplate")
private RestTemplate simpleRestTemplate;
Hope this helps

Resources