In general we set timeout for the okHttp client and we use single instance of that client. So, we can't change the timeout for that client once it's generated.
How to change the timeout for a particular request ?? Is there anyway
to do it without creating new client??
It's very common that some calls take more time atleast 1/2 per app, which needs more timeout than others. it would be great if request can override the default timeout.
Updating to Retrofit 2.5.0 you can use Invocation class
New: Invocation class provides a reference to the invoked method and argument list as a tag on the underlying OkHttp Call. This can be accessed from an OkHttp interceptor for things like logging, analytics, or metrics aggregation.
In that way, you can create a SpecificTimeout annotation to be used only on the requests you need to set a different timeout duration and read its values on an OkHttp Interceptor to change the timeout at that point.
Custom Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface SpecificTimeout {
int duration();
TimeUnit unit();
}
Service Interface
public interface GitHubService {
#GET("/users")
#SpecificTimeout(duration = 5000, unit = TimeUnit.MILLISECONDS)
Call<List<User>> listUsers();
#GET("/repos")
#SpecificTimeout(duration = 15000, unit = TimeUnit.MILLISECONDS)
Call<List<Repo>> listRepos();
}
Interceptor
class TimeoutInterceptor implements Interceptor {
#NonNull
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
final Invocation tag = request.tag(Invocation.class);
final Method method = tag != null ? tag.method() : null;
final SpecificTimeout timeout = method != null ? method.getAnnotation(SpecificTimeout.class) : null;
if (timeout != null) {
return chain.withReadTimeout(timeout.duration(), timeout.unit())
.withConnectTimeout(timeout.duration(), timeout.unit())
.withWriteTimeout(timeout.duration(), timeout.unit())
.proceed(request);
}
return chain.proceed(request);
}
}
OkHttp Builder
OkHttpClient okHttpClient = new OkHttpClient.Builder()
//default timeout for not annotated requests
.readTimeout(10000, TimeUnit.MILLISECONDS)
.connectTimeout(10000, TimeUnit.MILLISECONDS)
.writeTimeout(10000, TimeUnit.MILLISECONDS)
.addInterceptor(new TimeoutInterceptor())
.build();
See https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/PerCallSettings.java which creates shallow clones that only modify some settings.
// Copy to customize OkHttp for this request.
OkHttpClient client2 = client.newBuilder()
.readTimeout(3000, TimeUnit.MILLISECONDS)
.build();
In 3.9 it is possible to set this per request in an interceptor
https://github.com/square/okhttp/blob/36bd68aa3e93affb12504cd40454e64c6812019c/okhttp-tests/src/test/java/okhttp3/InterceptorTest.java#L747-L757
#Test public void chainWithReadTimeout() throws Exception {
Interceptor interceptor1 = new Interceptor() {
#Override public Response intercept(Chain chainA) throws IOException {
assertEquals(5000, chainA.readTimeoutMillis());
Chain chainB = chainA.withReadTimeout(100, TimeUnit.MILLISECONDS);
assertEquals(100, chainB.readTimeoutMillis());
return chainB.proceed(chainA.request());
}
};
}
I think a good way is to read a custom header on an interceptor and change the current timeout to calls that contains this specific header, it's works better than create a annotation and use reflection to read it. E.G:
class TimeoutInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
return request.header("Custom-Timeout")?.let {
val newTimeout = it.toInt()
chain.withReadTimeout(newTimeout, TimeUnit.MILLISECONDS)
.withConnectTimeout(newTimeout, TimeUnit.MILLISECONDS)
.withWriteTimeout(newTimeout, TimeUnit.MILLISECONDS)
.proceed(request)
} ?: chain.proceed(request)
}
}
Related
I have a spring boot application that has the below AuthFilter added for all rest apis exposed by the application. I want to test the below code that validates authorization token by calling a third party api call. I tried Mockito but how do I inject the mocked HttpPost, HttpClient etc object in the filter class?
Also what value do I pass to thirdPartyAPIUrl property which is configured in application.properties for test class
#Component
public class AuthTokenFilter implements Filter {
public boolean isAuthTokenValid(HttpServletRequest request, HttpServletResponse response) throws IOException {
String authorizationToken = request.getHeader(RequestHeaders.AUTHORIZATION.toString());
TokenRequest validateTokenRequest = new TokenRequest();
validateTokenRequest.setToken(authorizationToken);
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
HttpPost httpPost = new HttpPost(this.thirdPartyAPIUrl); //fetched through application.properties
httpPost.setHeader("Content-type", "application/json");
StringEntity requestBody = new StringEntity(new Gson().toJson(validateTokenRequest));
httpPost.setEntity(requestBody);
try (CloseableHttpResponse validateTokenResponse = httpclient.execute(httpPost)) {
HttpEntity rEntity = validateTokenResponse.getEntity();
TokenResponse tokenResponse = new ObjectMapper().readValue(rEntity.getContent(),
TokenResponse.class);
logger.debug("API Response Object : {}", tokenResponse);
}
}
return false; //temporary
}
}
Thanks!
I would recommend avoiding mocking HttpPost etc and instead just mocking the third-party server. My preferred tool to use for this is wiremock
Here is an example of how it would be used:
(make sure to import this for options, caused me a lot of headaches ;) )
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
... code
static WireMockServer wireMockServer = new WireMockServer(options().port(8080));
#BeforeAll
static void init() {
wireMockServer.start();
}
//this is for the case that you have multiple test suites that mock the server, to avoid conflicts with ports
#AfterAll
static void releaseResource() {
wireMockServer.stop();
}
#Test
void test() {
wireMockServer.stubFor(post("/endpoint").willReturn(aResponse().withStatus(200)));
... more code
filter.isAuthTokenValid(request, response);
}
I'm building a reverse-proxy for uploading large files (multiple gigabytes), and therefore want to use a streaming model that does not buffer entire files. Large buffers would introduce latency and, more importantly, they could result in out-of-memory errors.
My client class contains
#Autowired private RestTemplate restTemplate;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
int REST_TEMPLATE_MODE = 1; // 1=streams, 2=streams, 3=buffers
return
REST_TEMPLATE_MODE == 1 ? new RestTemplate() :
REST_TEMPLATE_MODE == 2 ? (new RestTemplateBuilder()).build() :
REST_TEMPLATE_MODE == 3 ? restTemplateBuilder.build() : null;
}
and
public void upload_via_streaming(InputStream inputStream, String originalname) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
InputStreamResource inputStreamResource = new InputStreamResource(inputStream) {
#Override public String getFilename() { return originalname; }
#Override public long contentLength() { return -1; }
};
MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>();
body.add("myfile", inputStreamResource);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body,headers);
String response = restTemplate.postForObject(UPLOAD_URL, requestEntity, String.class);
System.out.println("response: "+response);
}
This is working, but notice my REST_TEMPLATE_MODE value controls whether or not it meets my streaming requirement.
Question: Why does REST_TEMPLATE_MODE == 3 result in full-file buffering?
References:
How to forward large files with RestTemplate?
How to send Multipart form data with restTemplate Spring-mvc
Spring - How to stream large multipart file uploads to database without storing on local file system -- establishing the InputStream
How to autowire RestTemplate using annotations
Design notes and usage caveats, also: restTemplate does not support streaming downloads
In short, the instance of RestTemplateBuilder provided as an #Bean by Spring Boot includes an interceptor (filter) associated with actuator/metrics -- and the interceptor interface requires buffering of the request body into a simple byte[].
If you instantiate your own RestTemplateBuilder or RestTemplate from scratch, it won't include this by default.
I seem to be the only person visiting this post, but just in case it helps someone before I get around to posting a complete solution, I've found a big clue:
restTemplate.getInterceptors().forEach(item->System.out.println(item));
displays...
org.SF.boot.actuate.metrics.web.client.MetricsClientHttpRequestInterceptor
If I clear the interceptor list via setInterceptors, it solves the problem. Furthermore, I found that any interceptor, even if it only performs a NOP, will introduce full-file buffering.
public class SimpleClientHttpRequestFactory { ...
I have explicitly set bufferRequestBody = false, but apparently this code is bypassed if interceptors are used. This would have been nice to know earlier...
#Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());
if (this.bufferRequestBody) {
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
}
else {
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
}
}
public abstract class InterceptingHttpAccessor extends HttpAccessor { ...
This shows that the InterceptingClientHttpRequestFactory is used if the list of interceptors is not empty.
/**
* Overridden to expose an {#link InterceptingClientHttpRequestFactory}
* if necessary.
* #see #getInterceptors()
*/
#Override
public ClientHttpRequestFactory getRequestFactory() {
List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
else {
return super.getRequestFactory();
}
}
class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest { ...
The interfaces make it clear that using InterceptingClientHttpRequest requires buffering body to a byte[]. There is not an option to use a streaming interface.
#Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
I'm developing REST service which, in turn, will query slow legacy system so response time will be measured in seconds. We also expect massive load so I was thinking about asynchronous/non-blocking approaches to avoid hundreds of "servlet" threads blocked on calls to slow system.
As I see this can be implemented using AsyncContext which is present in new servlet API specs. I even developed small prototype and it seems to be working.
On the other hand it looks like I can achieve the same using Spring WebFlux.
Unfortunately I did not find any example where custom "backend" calls are wrapped with Mono/Flux. Most of the examples just reuse already-prepared reactive connectors, like ReactiveCassandraOperations.java, etc.
My data flow is the following:
JS client --> Spring RestController --> send request to Kafka topic --> read response from Kafka reply topic --> return data to client
Can I wrap Kafka steps into Mono/Flux and how to do this?
How my RestController method should look like?
Here is my simple implementation which achieves the same using Servlet 3.1 API
//took the idea from some Jetty examples
public class AsyncRestServlet extends HttpServlet {
...
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String result = (String) req.getAttribute(RESULTS_ATTR);
if (result == null) { //data not ready yet: schedule async processing
final AsyncContext async = req.startAsync();
//generate some unique request ID
String uid = "req-" + String.valueOf(req.hashCode());
//share it to Kafka receive together with AsyncContext
//when Kafka receiver will get the response it will put it in Servlet request attribute and call async.dispatch()
//This doGet() method will be called again and it will send the response to client
receiver.rememberKey(uid, async);
//send request to Kafka
sender.send(uid, param);
//data is not ready yet so we are releasing Servlet thread
return;
}
//return result as html response
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println(result);
out.close();
}
Here's a short example - Not the WebFlux client you probably had in mind, but at least it would enable you to utilize Flux and Mono for asynchronous processing, which I interpreted to be the point of your question. The web objects should work without additional configurations, but of course you will need to configure Kafka as the KafkaTemplate object will not work on its own.
#Bean // Using org.springframework.web.reactive.function.server.RouterFunction<ServerResponse>
public RouterFunction<ServerResponse> sendMessageToTopic(KafkaController kafkaController){
return RouterFunctions.route(RequestPredicates.POST("/endpoint"), kafkaController::sendMessage);
}
#Component
public class ResponseHandler {
public getServerResponse() {
return ServerResponse.ok().body(Mono.just(Status.SUCCESS), String.class);
}
}
#Component
public class KafkaController {
public Mono<ServerResponse> auditInvalidTransaction(ServerRequest request) {
return request.bodyToMono(TopicMsgMap.class)
// your HTTP call may not return immediately without this
.subscribeOn(Schedulers.single()) // for a single worker thread
.flatMap(topicMsgMap -> {
MyKafkaPublisher.sendMessages(topicMsgMap);
}.flatMap(responseHandler::getServerResponse);
}
}
#Data // model class just to easily convert the ServerRequest (from json, for ex.)
// + ~#constructors
public class TopicMsgMap() {
private Map<String, String> topicMsgMap;
}
#Service // Using org.springframework.kafka.core.KafkaTemplate<String, String>
public class MyKafkaPublisher {
#Autowired
private KafkaTemplate<String, String> template;
#Value("${topic1}")
private String topic1;
#Value("${topic2}")
private String topic2;
public void sendMessages(Map<String, String> topicMsgMap){
topicMsgMap.forEach((top, msg) -> {
if (topic.equals("topic1") kafkaTemplate.send(topic1, message);
if (topic.equals("topic2") kafkaTemplate.send(topic2, message);
});
}
}
Guessing this isn't the use-case you had in mind, but hope you find this general structure useful.
There is several approaches including KafkaReplyingRestTemplate for this problem but continuing your approach in servlet api's the solution will be something like this in spring Webflux.
Your Controller method looks like this:
#RequestMapping(path = "/completable-future", method = RequestMethod.POST)
Mono<Response> asyncTransaction(#RequestBody RequestDto requestDto, #RequestHeader Map<String, String> requestHeaders) {
String internalTransactionId = UUID.randomUUID().toString();
kafkaSender.send(Request.builder()
.transactionId(requestHeaders.get("transactionId"))
.internalTransactionId(internalTransactionId)
.sourceIban(requestDto.getSourceIban())
.destIban(requestDto.getDestIban())
.build());
CompletableFuture<Response> completableFuture = new CompletableFuture();
taskHolder.pushTask(completableFuture, internalTransactionId);
return Mono.fromFuture(completableFuture);
}
Your taskHolder component will be something like this:
#Component
public class TaskHolder {
private Map<String, CompletableFuture> taskHolder = new ConcurrentHashMap();
public void pushTask(CompletableFuture<Response> task, String transactionId) {
this.taskHolder.put(transactionId, task);
}
public Optional<CompletableFuture> remove(String transactionId) {
return Optional.ofNullable(this.taskHolder.remove(transactionId));
}
}
And finally your Kafka ResponseListener looks like this:
#Component
public class ResponseListener {
#Autowired
TaskHolder taskHolder;
#KafkaListener(topics = "reactive-response-topic", groupId = "test")
public void listen(Response response) {
taskHolder.remove(response.getInternalTransactionId()).orElse(
new CompletableFuture()).complete(response);
}
}
In this example I used internalTransactionId as CorrelationId but you can use "kafka_correlationId" that is a known kafka header.
We wrote a small Spring Boot REST application, which performs a REST request on another REST endpoint.
#RequestMapping("/api/v1")
#SpringBootApplication
#RestController
#Slf4j
public class Application
{
#Autowired
private WebClient webClient;
#RequestMapping(value = "/zyx", method = POST)
#ResponseBody
XyzApiResponse zyx(#RequestBody XyzApiRequest request, #RequestHeader HttpHeaders headers)
{
webClient.post()
.uri("/api/v1/someapi")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(request.getData()))
.exchange()
.subscribeOn(Schedulers.elastic())
.flatMap(response ->
response.bodyToMono(XyzServiceResponse.class).map(r ->
{
if (r != null)
{
r.setStatus(response.statusCode().value());
}
if (!response.statusCode().is2xxSuccessful())
{
throw new ProcessResponseException(
"Bad status response code " + response.statusCode() + "!");
}
return r;
}))
.subscribe(body ->
{
// Do various things
}, throwable ->
{
// This section handles request errors
});
return XyzApiResponse.OK;
}
}
We are new to Spring and are having trouble writing a Unit Test for this small code snippet.
Is there an elegant (reactive) way to mock the webClient itself or to start a mock server that the webClient can use as an endpoint?
We accomplished this by providing a custom ExchangeFunction that simply returns the response we want to the WebClientBuilder:
webClient = WebClient.builder()
.exchangeFunction(clientRequest ->
Mono.just(ClientResponse.create(HttpStatus.OK)
.header("content-type", "application/json")
.body("{ \"key\" : \"value\"}")
.build())
).build();
myHttpService = new MyHttpService(webClient);
Map<String, String> result = myHttpService.callService().block();
// Do assertions here
If we want to use Mokcito to verify if the call was made or reuse the WebClient accross multiple unit tests in the class, we could also mock the exchange function:
#Mock
private ExchangeFunction exchangeFunction;
#BeforeEach
void init() {
WebClient webClient = WebClient.builder()
.exchangeFunction(exchangeFunction)
.build();
myHttpService = new MyHttpService(webClient);
}
#Test
void callService() {
when(exchangeFunction.exchange(any(ClientRequest.class)))
.thenReturn(buildMockResponse());
Map<String, String> result = myHttpService.callService().block();
verify(exchangeFunction).exchange(any());
// Do assertions here
}
Note: If you get null pointer exceptions related to publishers on the when call, your IDE might have imported Mono.when instead of Mockito.when.
Sources:
WebClient
javadoc
WebClient.Builder
javadoc
ExchangeFunction
javadoc
With the following method it was possible to mock the WebClient with Mockito for calls like this:
webClient
.get()
.uri(url)
.header(headerName, headerValue)
.retrieve()
.bodyToMono(String.class);
or
webClient
.get()
.uri(url)
.headers(hs -> hs.addAll(headers));
.retrieve()
.bodyToMono(String.class);
Mock method:
private static WebClient getWebClientMock(final String resp) {
final var mock = Mockito.mock(WebClient.class);
final var uriSpecMock = Mockito.mock(WebClient.RequestHeadersUriSpec.class);
final var headersSpecMock = Mockito.mock(WebClient.RequestHeadersSpec.class);
final var responseSpecMock = Mockito.mock(WebClient.ResponseSpec.class);
when(mock.get()).thenReturn(uriSpecMock);
when(uriSpecMock.uri(ArgumentMatchers.<String>notNull())).thenReturn(headersSpecMock);
when(headersSpecMock.header(notNull(), notNull())).thenReturn(headersSpecMock);
when(headersSpecMock.headers(notNull())).thenReturn(headersSpecMock);
when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<String>>notNull()))
.thenReturn(Mono.just(resp));
return mock;
}
You can use MockWebServer by the OkHttp team. Basically, the Spring team uses it for their tests too (at least how they said here). Here is an example with reference to a source:
According to Tim's blog post let's consider that we have the following service:
class ApiCaller {
private WebClient webClient;
ApiCaller(WebClient webClient) {
this.webClient = webClient;
}
Mono<SimpleResponseDto> callApi() {
return webClient.put()
.uri("/api/resource")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "customAuth")
.syncBody(new SimpleRequestDto())
.retrieve()
.bodyToMono(SimpleResponseDto.class);
}
}
then the test could be designed in the following way (comparing to origin I changed the way how async chains should be tested in Reactor using StepVerifier):
class ApiCallerTest {
private final MockWebServer mockWebServer = new MockWebServer();
private final ApiCaller apiCaller = new ApiCaller(WebClient.create(mockWebServer.url("/").toString()));
#AfterEach
void tearDown() throws IOException {
mockWebServer.shutdown();
}
#Test
void call() throws InterruptedException {
mockWebServer.enqueue(new MockResponse().setResponseCode(200)
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.setBody("{\"y\": \"value for y\", \"z\": 789}")
);
//Asserting response
StepVerifier.create(apiCaller.callApi())
.assertNext(res -> {
assertNotNull(res);
assertEquals("value for y", res.getY());
assertEquals("789", res.getZ());
})
.verifyComplete();
//Asserting request
RecordedRequest recordedRequest = mockWebServer.takeRequest();
//use method provided by MockWebServer to assert the request header
recordedRequest.getHeader("Authorization").equals("customAuth");
DocumentContext context = >JsonPath.parse(recordedRequest.getBody().inputStream());
//use JsonPath library to assert the request body
assertThat(context, isJson(allOf(
withJsonPath("$.a", is("value1")),
withJsonPath("$.b", is(123))
)));
}
}
I use WireMock for integration testing. I think it is much better and supports more functions than OkHttp MockeWebServer. Here is simple example:
public class WireMockTest {
WireMockServer wireMockServer;
WebClient webClient;
#BeforeEach
void setUp() throws Exception {
wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort());
wireMockServer.start();
webClient = WebClient.builder().baseUrl(wireMockServer.baseUrl()).build();
}
#Test
void testWireMock() {
wireMockServer.stubFor(get("/test")
.willReturn(ok("hello")));
String body = webClient.get()
.uri("/test")
.retrieve()
.bodyToMono(String.class)
.block();
assertEquals("hello", body);
}
#AfterEach
void tearDown() throws Exception {
wireMockServer.stop();
}
}
If you really want to mock it I recommend JMockit. There isn't necessary call when many times and you can use the same call like it is in your tested code.
#Test
void testJMockit(#Injectable WebClient webClient) {
new Expectations() {{
webClient.get()
.uri("/test")
.retrieve()
.bodyToMono(String.class);
result = Mono.just("hello");
}};
String body = webClient.get()
.uri(anyString)
.retrieve()
.bodyToMono(String.class)
.block();
assertEquals("hello", body);
}
Wire mocks is suitable for integration tests, while I believe it's not needed for unit tests. While doing unit tests, I will just be interested to know if my WebClient was called with the desired parameters. For that you need a mock of the WebClient instance. Or you could inject a WebClientBuilder instead.
Let's consider the simplified method which does a post request like below.
#Service
#Getter
#Setter
public class RestAdapter {
public static final String BASE_URI = "http://some/uri";
public static final String SUB_URI = "some/endpoint";
#Autowired
private WebClient.Builder webClientBuilder;
private WebClient webClient;
#PostConstruct
protected void initialize() {
webClient = webClientBuilder.baseUrl(BASE_URI).build();
}
public Mono<String> createSomething(String jsonDetails) {
return webClient.post()
.uri(SUB_URI)
.accept(MediaType.APPLICATION_JSON)
.body(Mono.just(jsonDetails), String.class)
.retrieve()
.bodyToMono(String.class);
}
}
The method createSomething just accepts a String, assumed as Json for simplicity of the example, does a post request on a URI and returns the output response body which is assumed as a String.
The method can be unit tested as below, with StepVerifier.
public class RestAdapterTest {
private static final String JSON_INPUT = "{\"name\": \"Test name\"}";
private static final String TEST_ID = "Test Id";
private WebClient.Builder webClientBuilder = mock(WebClient.Builder.class);
private WebClient webClient = mock(WebClient.class);
private RestAdapter adapter = new RestAdapter();
private WebClient.RequestBodyUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class);
private WebClient.RequestBodySpec requestBodySpec = mock(WebClient.RequestBodySpec.class);
private WebClient.RequestHeadersSpec requestHeadersSpec = mock(WebClient.RequestHeadersSpec.class);
private WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);
#BeforeEach
void setup() {
adapter.setWebClientBuilder(webClientBuilder);
when(webClientBuilder.baseUrl(anyString())).thenReturn(webClientBuilder);
when(webClientBuilder.build()).thenReturn(webClient);
adapter.initialize();
}
#Test
#SuppressWarnings("unchecked")
void createSomething_withSuccessfulDownstreamResponse_shouldReturnCreatedObjectId() {
when(webClient.post()).thenReturn(requestBodyUriSpec);
when(requestBodyUriSpec.uri(RestAdapter.SUB_URI))
.thenReturn(requestBodySpec);
when(requestBodySpec.accept(MediaType.APPLICATION_JSON)).thenReturn(requestBodySpec);
when(requestBodySpec.body(any(Mono.class), eq(String.class)))
.thenReturn(requestHeadersSpec);
when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.bodyToMono(String.class)).thenReturn(Mono.just(TEST_ID));
ArgumentCaptor<Mono<String>> captor
= ArgumentCaptor.forClass(Mono.class);
Mono<String> result = adapter.createSomething(JSON_INPUT);
verify(requestBodySpec).body(captor.capture(), eq(String.class));
Mono<String> testBody = captor.getValue();
assertThat(testBody.block(), equalTo(JSON_INPUT));
StepVerifier
.create(result)
.expectNext(TEST_ID)
.verifyComplete();
}
}
Note that the 'when' statements test all the parameters except the request Body. Even if one of the parameters mismatches, the unit test fails, thereby asserting all these. Then, the request body is asserted in a separate verify and assert as the 'Mono' cannot be equated. The result is then verified using step verifier.
And then, we can do an integration test with wire mock, as mentioned in the other answers, to see if this class wires properly, and calls the endpoint with the desired body, etc.
I have tried all the solutions in the already given answers here.
The answer to your question is:
It depends if you want to do Unit testing or Integration testing.
For unit testing purpose, mocking the WebClient itself is too verbose and require too much code. Mocking ExchangeFunction is simpler and easier.
For this, the accepted answer must be #Renette 's solution.
For integration testing the best is to use OkHttp MockWebServer.
Its simple to use an flexible. Using a server allows you to handle some error cases you otherwise need to handle manually in a Unit testing case.
With spring-cloud-starter-contract-stub-runner you can use Wiremock to mock the API responses. Here you can find a working example I described on medium. The AutoConfigureMockMvc annotation starts a Wiremock server before your test, exposing everything you have in the classpath:/mappings location (probably src/test/resources/mappings on disk).
#SpringBootTest
#AutoConfigureMockMvc
#AutoConfigureWireMock(port = 0)
class BalanceServiceTest {
private static final Logger log = LoggerFactory.getLogger(BalanceServiceTest.class);
#Autowired
private BalanceService service;
#Test
public void test() throws Exception {
assertNotNull(service.getBalance("123")
.get());
}
}
Here is an example for what a mapping file looks like. The balance.json file contains any json content you need. You can also mimic response delays or failures in static configuration files or programatically. More info on their website.
{
"request": {
"method": "GET",
"url": "/v2/accounts/123/balance"
},
"response": {
"status": 200,
"delayDistribution": {
"type": "lognormal",
"median": 1000,
"sigma": 0.4
},
"headers": {
"Content-Type": "application/json",
"Cache-Control": "no-cache"
},
"bodyFileName": "balance.json"
}
}
I wanted to use webclient for unit testing, but mockito was too complex to setup, so i created a library which can be used to build mock webclient in unit tests. This also verifies the url, method, headers and request body before dispatching the response.
FakeWebClientBuilder fakeWebClientBuilder = FakeWebClientBuilder.useDefaultWebClientBuilder();
FakeRequestResponse fakeRequestResponse = new FakeRequestResponseBuilder()
.withRequestUrl("https://google.com/foo")
.withRequestMethod(HttpMethod.POST)
.withRequestBody(BodyInserters.fromFormData("foo", "bar"))
.replyWithResponse("test")
.replyWithResponseStatusCode(200)
.build();
WebClient client =
FakeWebClientBuilder.useDefaultWebClientBuilder()
.baseUrl("https://google.com")
.addRequestResponse(fakeRequestResponse)
.build();
// Our webclient will return `test` when called.
// This assertion would check if all our enqueued responses are dequeued by the class or method we intend to test.
Assertions.assertTrue(fakeWebClientBuilder.assertAllResponsesDispatched());
I highly recommend using Okhttp MockWebServer over mocking. The reason being MockWebServer is a much much cleaner approach.
Below is the code template you can use for unit testing WebClient.
class Test {
private ClassUnderTest classUnderTest;
public static MockWebServer mockWebServer;
#BeforeAll
static void setUp() throws IOException {
mockWebServer = new MockWebServer();
mockWebServer.start();
}
#BeforeEach
void initialize() {
var httpUrl = mockWebServer.url("/xyz");
var webClient = WebClient.create(httpUrl.toString());
classUnderTest = new ClassUnderTest(webClient);
}
#Test
void testMehod() {
var mockResp = new MockResponse();
mockResp.setResponseCode(200);
mockResp.addHeader("Content-Type", "application/json");
mockResp.setBody(
"{\"prop\":\"some value\"}");
mockWebServer.enqueue(mockResp);
// This enqueued response will be returned when webclient is invoked
...
...
classUnderTest.methodThatInvkesWebClient();
...
...
}
#AfterAll
static void tearDown() throws IOException {
mockWebServer.shutdown();
}
}
Pay special attention to the initialize method. That's the only thing tricky here.
Path /xyz is not the base url, rather your resource path.
You don't need to tell the base url to MockWebServer.
Reason being, MockWebServer will spin up a server on the local host with some random port. And if you provide your own base url, your unit test will fail.
mockWebServer.url("/xyz")
This will give you base url i.e. the host and port on which MockWebServer is listening plus the resource path, say localhost:8999/xyz. You will need to create WebClient with this url.
WebClient.create(httpUrl.toString())
This will create the WebClient that make calls to the MockWebServer for your unit tests.
I'm looking to do something like the following:
#HystrixCommand(fallbackMethod = "returnLastGoodCachedCopy")
void performRequest(String someArg) {
// Make HTTP request using RestTemplate configured with EHCache
}
void returnLastGoodCachedCopy(String someArg) {
// Return the last successful response
}
For a little more background I am using Spring Boot, and setting up the RestTemplate like so to use EHCache:
#Bean
public RestTemplate restTemplate() {
return new RestTemplate(clientHttpRequestFactory());
}
#Bean
public CloseableHttpClient httpClient() {
CacheConfig cacheConfig = CacheConfig.custom()
.setMaxCacheEntries(this.httpClientProperties.getMaxCacheEntries())
.setMaxObjectSize(this.httpClientProperties.getMaxObjectSize())
.setHeuristicCachingEnabled(this.httpClientProperties.getHeuristicLifetimeEnabled())
.setHeuristicDefaultLifetime(this.httpClientProperties.getHeuristicLifetimeSeconds()).build();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(this.httpClientProperties.getConnectTimeout())
.setSocketTimeout(this.httpClientProperties.getSocketTimeout()).build();
Ehcache httpEhcache = (Ehcache) this.ehCacheManager.getCache(httpClientProperties.getCacheName())
.getNativeCache();
EhcacheHttpCacheStorage ehcacheHttpCacheStorage = new EhcacheHttpCacheStorage(httpEhcache);
CloseableHttpClient cachingClient = CachingHttpClients.custom().setCacheConfig(cacheConfig)
.setHttpCacheStorage(ehcacheHttpCacheStorage).setDefaultRequestConfig(requestConfig).build();
return cachingClient;
}
private ClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient());
return factory;
}
So my one thought was to use the same request EHCache, but I'm not sure that's an appropriate solution considering that cache is based on cache-control headers, which I'd like to be separated from so I can return a valid response regardless if it is expired or not.
My other thought was to configure a separate EHCache cache, and store the responses myself, then I can access those more easily considering I set the format of the key and the value.
Before I go down this path I want to see if there is already anything built into Hystrix that handles this situation, or if there are any other recommended approaches.