Trying to mock restClient external API but it is invoking the actual API in java - spring

I am trying to mock restClient external API but it is invoking the actual API instead of mocking it.
Kindly help as I am not sure where I am going wrong.
I tried mocking the call and a few more other things but it didn't work.
public class TestService
{
private static final String EXTERNAL_API = "http://LinktoExternalAPI/";
#Autowired
RestTemplate restTemplate;
public Map<String, String> countryCodes()
{
Map<String, String> map = new TreeMap<>();
try
{
ResponseEntity<testCountry[]> responseEntity = restTemplate
.getForEntity(EXTERNAL_API
, testCountry[].class);
List<testCountry> testCountryList = Arrays.asList(responseEntity.getBody());
map = testCountryList.stream()
.collect(Collectors.toMap(testCountry::getCode, testCountry::getName));
}
catch (HttpClientErrorException | HttpServerErrorException httpClientOrServerExc)
{
}
return map;
}
}
Test case for this is below:
#RunWith(PowerMockRunner.class)
public class TestServiceTest
{
#InjectMocks
TestService testService;
#Mock
RestTemplate restTemplate;
private static final String EXTERNAL_API = "http://LinktoExternalAPI/";
#Test
public void testCountryCodes(){
TestCountry testCountry = new TestCountry();
testCountry.setCode("JPN");
testCountry.setName("Japan");
List<testCountry> testCountryList = new ArrayList<testCountry>();
testCountryList.add(testCountry);
Mockito.when(restTemplate.getForEntity(EXTERNAL_API, testCountry[].class)).thenReturn(new ResponseEntity(testCountryList, HttpStatus.OK));
Map<String, String> result = testService.countryCodes();
// result is pulling the actual size of the api instead of mocking and sending me testCountryList size.
<Will mention assertion here>
}
The result is pulling the actual size of the API instead of mocking and sending me testCountryList size.

The reason behind the actual API being called is probably that the URL you are mocking is not exactly the same as that being generated at runtime, because of which a mock is not found and actual API is called.
In these cases, you can use Mockito.any().
So the mock code will be Mockito.when(restTemplate.getForEntity(Mockito.any(), Mockito.any())).thenReturn(new ResponseEntity(testCountryList, HttpStatus.OK));
#RunWith(MockitoJUnitRunner.class)
public class TestServiceTest {
#InjectMocks
private TestService testService;
#Mock
private RestTemplate restTemplate;
#Test
public void testCountryCodes(){
TestCountry testCountry = new TestCountry();
testCountry.setCode("JPN");
testCountry.setName("Japan");
TestCountry[] testCountryList = {
testCountry
};
Mockito.when(restTemplate.getForEntity(Mockito.anyString(), Mockito.any())).thenReturn(new ResponseEntity(testCountryList, HttpStatus.OK));
Map<String, String> result = testService.countryCodes();
// result is pulling the actual size of the API instead of mocking and sending me testCountryList size.
}
}
Also try using #RunWith(MockitoJUnitRunner.class) instead of PowerMockRunner.class since you don't seem to be needing the PowerMock capabilities.

You are mocking the wrong method definition.
A getForObject method with the parameters String and Class does not exist. You need to define behaviour for this method.
Note that in your case the third parameter (the varargs) is not used, so it defaults to an empty array. However Mockito requires this information to mock the correct call.
Mockito.when(restTemplate.getForObject(any(String.class), any(Class.class), ArgumentMatchers.<Object>any()))
.thenReturn(result);
For a more complete example check my answer here.

Related

Mockito is returning "java.lang.IllegalArgumentException: URI is not absolute" in RestTemplate.exchange Springboot

Mockito is returning "java.lang.IllegalArgumentException: URI is not absolute" in RestTemplate.exchange. I am not sure why this is happening because it seems I am mocking the restTemplate properly and since I am seeing that exception, it seems that RestTemplate is not a mock.
Here is my class
#Component
public class RestTemplateWrapper {
private static final Logger LOGGER = LoggerFactory.getLogger(RestTemplateWrapper.class);
public <T> ResponseEntity<T> callWebServiceGET(String url,HttpEntity<?> httpEntity,
ParameterizedTypeReference<T> parameterizedTypeReference) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<T> response=null;
LOGGER.trace("Entered callWebServiceGET");
LOGGER.info("Calling WebService {}", url);
try {
response=restTemplate.exchange(url, HttpMethod.GET, httpEntity, parameterizedTypeReference);
} catch (HttpClientErrorException e) {
if (HttpStatus.NOT_FOUND.equals(e.getStatusCode())) {
LOGGER.error("Service Unavailable - Code 404 returned. " + url + e.getMessage());
} else if (HttpStatus.UNAUTHORIZED.equals(e.getStatusCode())) {
LOGGER.error("Token Expired- Code 401 returned. " + e.getMessage());
} else if (HttpStatus.BAD_REQUEST.equals(e.getStatusCode())) {
LOGGER.error("Bad Input, 400 returned.{} {} ", url , e.getMessage(), e);
} else {
LOGGER.error("WEB Service Failure. " + e.getMessage());
}
}
return response;
}
}
And here is my TestCase:
#PrepareForTest({RestTemplateWrapper.class})
public class RestTemplateWrapperTest {
#Mock
private RestTemplate mockRestTemplate;
#InjectMocks
private RestTemplateWrapper webUtils;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void callWebServiceGET_OK() {
HttpEntity<String> httpEntity= new ResponseEntity<>(HttpStatus.OK);
ResponseEntity<String> entityResponse=new ResponseEntity<>("MOCK_RESPONSE", HttpStatus.OK);
when(mockRestTemplate.exchange(eq("/objects/get-objectA"), eq(HttpMethod.GET), eq(httpEntity),any(
ParameterizedTypeReference.class))).thenReturn(
entityResponse);
ResponseEntity<String> mockResponse= webUtils.callWebServiceGET("",null, new ParameterizedTypeReference<String>(){
});
//verify(mockRestTemplate,times(1)).exchange(Matchers.anyString(), Matchers.any(), Matchers.any());
Assert.assertEquals("MOCK_RESPONSE",mockResponse.getBody());
}
}
The response:
URI is not absolute
java.lang.IllegalArgumentException: URI is not absolute
at java.net.URI.toURL(URI.java:1088)
at org.springframework.http.client.SimpleClientHttpRequestFactory.createRequest(SimpleClientHttpRequestFactory.java:145)
at org.springframework.http.client.support.HttpAccessor.createRequest(HttpAccessor.java:87)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:727)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:666)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:604)
at com.project.di.tp.purchaseorderservice.utils.RestTemplateWrapper.callWebServiceGET(RestTemplateWrapper.java:29)
at com.project.di.tp.purchaseorderservice.utils.RestTemplateWrapperTest.callWebServiceGET_OK(RestTemplateWrapperTest.java:51)
Any idea about how to solve this issue? I have been trying like 4 hours.
I found the solution, it seems the problem is that my class is RestTemplateWrapper is creating a instance inside callWebServiceGET therefore mockito can`t mock that object. If if set the object outside the method, it works but I dont want to do that.
Is there any way to mock a object that is inside a method?
Although it is not stated explicitly in the JavaDocs it is the case that you have to provide an absolute URL there.
This is because you do nowhere provide a base URL where a relative URL would be relative to. You could not enter "/objects/get-objectA" as URL in your browser either.
So I would suggest that you use something like "http://example.com/objects/get-objectA" instead for the first parameter:
when(mockRestTemplate.exchange(
eq("http://example.com/bla"),
eq(HttpMethod.GET),
isNull(HttpEntity.class),
any(ParameterizedTypeReference.class))).
thenReturn(entityResponse);
ResponseEntity<String> mockResponse =
webUtils.callWebServiceGET(
"http://example.com/bla",
null,
new ParameterizedTypeReference<String>(){});
Please note that the call to webUtils.callWebServiceGET with given parameters would not make Mockito return the wanted answer, so I changed for one the URL in the call to the absolute URL you are expecting in the Mockito.when and also changed the parameter expected there to be a typed null (typed to match the method signature).
UPDATE:
As you found out by yourself already, your Mockito.when doesn't work because you do not use the created mock from the test in your tested method, but instead create a fresh instance of RestTemplate in each call of callWebServiceGET. (Don't know why I didn't see it earlier, sorry!)
I recommend that instead you inject the RestTemplate into the tested class with a constructor:
private final RestTemplate restTemplate;
public RestTemplateWrapper(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
// remove the following line in the method callWebServiceGET:
// RestTemplate restTemplate = new RestTemplate();
With this code, Spring will automatically inject your mocked RestTemplate into the test, but for running the production code you need to add a bean to provide a RestTemplate for injection.
Add this to a Configuration class where you also define other beans:
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
// Do any additional configuration here
return builder.build();
}
(Found this code snippet in an answer to How to autowire RestTemplate using annotations)
And as a general advice for testing: try to avoid the use of the new operator in any code you want to test, but use injection instead. If you need to create multiple instances (e.g. in a loop, etc.) try to inject a factory that creates the instances for you - so in the test you can mock the factory.

How to write Mockito for Rest template 2 based response from RestTemplate 1

#Test
public void getEventsByOrg() throws Exception {
String mockResposne = getXMLFromFile("classpath:OrgResponse.xml");
ResponseEntity<String> response = new ResponseEntity<>(mockResposne, HttpStatus.OK);
when(restTemplate.exchange(any(String.class), any(), any(HttpEntity.class), any(Class.class)))
.thenReturn(response);
ResponseEntity<List<OCVEvents>> ocvEvents = eventService.getEventsByGlobalKey(eventIdOrg, traceId);
verify(restTemplate).exchange(any(String.class), any(), captor.capture(), any(Class.class));
Events event = ocvEvents.getBody().get(0);
Events eventsPerson = new ObjectMapper().readValue(ResourceUtils.getFile("classpath:EventOrg.json"), Events.class);
assertThat(event.getHeader()).isEqualTo(eventsPerson.getHeader());
Now i have another Rest Call inside eventsService
How to write unit test for that
I need to hit a Rest API(1) & get the response, based on response i need to hit another Rest API (2)
i need to write the mockit0 for this class
If you have multiple rest api calls being made in your service class , you will have to mock all the api calls to return a mock data for your test to run. Just like you mocked the response for the first api call, add a mock response for the second api call before calling your service in you test. Instead of using any() as the argument matcher specify the particular url that you are going to be calling from the code to differentiate between the two api call mocks.
If you are on Springboot and using spring-boot-test for Integration testing , then you could use TestRestTemplate like:
TestRestTemplate testRestTemplate = new TestRestTemplate();
ResponseEntity<String> response = testRestTemplate.
getForEntity(FOO_RESOURCE_URL + "/1", String.class);
assertThat(response.getStatusCode(), equalTo(HttpStatus.OK));
or If you are just having a Unit test case using Mockito for instance then :
#Mock
private RestTemplate restTemplate;
#InjectMocks
private EventService eventService = new EventService();
#Test
public void givenMockingIsDoneByMockito_whenGetIsCalled_shouldReturnMockedObject() {
SomeObject instance = new SomeObject(“E001”, "Eric Simmons");
Mockito
.when(restTemplate.getForEntity(
“http://localhost:8080/test/E001”, SomeObject.class))
.thenReturn(new ResponseEntity(instance, HttpStatus.OK));
SomeObject returnedObject = eventService.getEventsByGlobalKey(id);
Assert.assertEquals(instance, returnedObject);
}

Can I use Spring WebFlux to implement REST services which get data through Kafka request/response topics?

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.

How to mock Spring WebFlux WebClient?

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.

Mocking Jersey Client using Mockito

Hello I am testing Jersey Client 1.19 version using JUnit and Mockito. I am struggling on getting or reading the entity. I don't know how to proceed from there and i am also getting IllegalStateException.
Below is the code that i am using to create mock objects for client response.
public class MockJerseyClient {
private ClientConfiguration clientConfig;
private Client client;
private WebTarget webTarget;
private Invocation.Builder invocationBuilder;
private Response response;
private RetrieveBillingResponseXMLReader xmlReader;
private ResponseBuilder responseBuilder;
public MockJerseyClient(String uri, int status, String contentType, String content) {
// Mock Objects
clientConfig = Mockito.mock(ClientConfiguration.class);
client = Mockito.mock(Client.class);
clientConfig.createClient();
webTarget = Mockito.mock(WebTarget.class);
clientConfig.createWebResource(uri);
invocationBuilder = Mockito.mock(Invocation.Builder.class);
xmlReader = new RetrieveBillingResponseXMLReader();
responseBuilder = Response.accepted();
response = responseBuilder.build();
// Rule for Client...
Mockito.when(client.target(uri)).thenReturn(webTarget);
// Rule for ClientConfiguration...
Mockito.when(clientConfig.createWebResource(Mockito.anyString())).thenReturn(webTarget);
// Rules for WebTarget...
Mockito.when(webTarget.path(Mockito.anyString())).thenReturn(webTarget);
Mockito.when(webTarget.register(xmlReader.getClass())).thenReturn(webTarget);
Mockito.when(webTarget.queryParam(Mockito.anyString(), Mockito.anyObject())).thenReturn(webTarget);
Mockito.when(webTarget.request()).thenReturn(invocationBuilder);
// Rules for Invocation.Builder...
Mockito.when(invocationBuilder.header(Mockito.anyString(), Mockito.anyObject())).thenReturn(invocationBuilder);
Mockito.when(invocationBuilder.accept(Mockito.anyString())).thenReturn(invocationBuilder);
Mockito.when(invocationBuilder.get(Response.class)).thenReturn(response);
Mockito.when(response.readEntity(String.class)).thenReturn(content);
// String entity = response.readEntity(String.class);
response.close();
} // end of constructor...
public ClientConfiguration getClientConfiguration() {
return clientConfig;
} // end of method...
If someone could help me how to read the entity based on different content types.
Thanks
Tests like this are extremely brittle, as they are too low-level... and in this case you don't assert a lot of things, so you're not testing good enough. It's generally better not to repeat the calls you'll have to make in your client, but instead mock the resource you want to access.
An easy way to do so, is to use a DropwizardClientRule. There's an example in the tests.

Resources