I have a service in which I need to ask an outside server via rest for some information:
public class SomeService {
public List<ObjectA> getListofObjectsA() {
List<ObjectA> objectAList = new ArrayList<ObjectA>();
ParameterizedTypeReference<List<ObjectA>> typeRef = new ParameterizedTypeReference<List<ObjectA>>() {};
ResponseEntity<List<ObjectA>> responseEntity = restTemplate.exchange("/objects/get-objectA", HttpMethod.POST, new HttpEntity<>(ObjectAList), typeRef);
return responseEntity.getBody();
}
}
How can I write a JUnit test for getListofObjectsA()?
I have tried with the below:
#RunWith(MockitoJUnitRunner.class)
public class SomeServiceTest {
private MockRestServiceServer mockServer;
#Mock
private RestTemplate restTemplate;
#Inject
private SomeService underTest;
#Before
public void setup() {
mockServer = MockRestServiceServer.createServer(restTemplate);
underTest = new SomeService(restTemplate);
mockServer.expect(requestTo("/objects/get-objectA")).andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("{json list response}", MediaType.APPLICATION_JSON));
}
#Test
public void testGetObjectAList() {
List<ObjectA> res = underTest.getListofObjectsA();
Assert.assertEquals(myobjectA, res.get(0));
}
However the above code does not work, it shows that responseEntitty is null. How can I correct my test to properly mock restTemplate.exchange?
This is an example with the non deprecated ArgumentMatchers class
when(restTemplate.exchange(
ArgumentMatchers.anyString(),
ArgumentMatchers.any(HttpMethod.class),
ArgumentMatchers.any(),
ArgumentMatchers.<Class<String>>any()))
.thenReturn(responseEntity);
You don't need MockRestServiceServer object. The annotation is #InjectMocks not #Inject. Below is an example code that should work
#RunWith(MockitoJUnitRunner.class)
public class SomeServiceTest {
#Mock
private RestTemplate restTemplate;
#InjectMocks
private SomeService underTest;
#Test
public void testGetObjectAList() {
ObjectA myobjectA = new ObjectA();
//define the entity you want the exchange to return
ResponseEntity<List<ObjectA>> myEntity = new ResponseEntity<List<ObjectA>>(HttpStatus.ACCEPTED);
Mockito.when(restTemplate.exchange(
Matchers.eq("/objects/get-objectA"),
Matchers.eq(HttpMethod.POST),
Matchers.<HttpEntity<List<ObjectA>>>any(),
Matchers.<ParameterizedTypeReference<List<ObjectA>>>any())
).thenReturn(myEntity);
List<ObjectA> res = underTest.getListofObjectsA();
Assert.assertEquals(myobjectA, res.get(0));
}
ResponseEntity<String> responseEntity = new ResponseEntity<String>("sampleBodyString", HttpStatus.ACCEPTED);
when(restTemplate.exchange(
Matchers.anyString(),
Matchers.any(HttpMethod.class),
Matchers.<HttpEntity<?>> any(),
Matchers.<Class<String>> any()
)
).thenReturn(responseEntity);
For me, I had to use Matchers.any(URI.class)
Mockito.when(restTemplate.exchange(Matchers.any(URI.class), Matchers.any(HttpMethod.class), Matchers.<HttpEntity<?>> any(), Matchers.<Class<Object>> any())).thenReturn(myEntity);
This work on my side.
ResourceBean resourceBean = initResourceBean();
ResponseEntity<ResourceBean> responseEntity
= new ResponseEntity<ResourceBean>(resourceBean, HttpStatus.ACCEPTED);
when(restTemplate.exchange(
Matchers.anyObject(),
Matchers.any(HttpMethod.class),
Matchers.<HttpEntity> any(),
Matchers.<Class<ResourceBean>> any())
).thenReturn(responseEntity);
The RestTemplate instance has to be a real object. It should work if you create a real instance of RestTemplate and make it #Spy.
#Spy
private RestTemplate restTemplate = new RestTemplate();
I used to get such an error. I found a more reliable solution. I have mentioned the import statements too which have worked for me. The below piece of code perfectly mocks restemplate.
import org.mockito.Matchers;
import static org.mockito.Matchers.any;
HttpHeaders headers = new Headers();
headers.setExpires(10000L);
ResponseEntity<String> responseEntity = new ResponseEntity<>("dummyString", headers, HttpStatus.OK);
when(restTemplate.exchange( Matchers.anyString(),
Matchers.any(HttpMethod.class),
Matchers.<HttpEntity<?>> any(),
Matchers.<Class<String>> any())).thenReturn(responseEntity);
If anyone has this problem while trying to mock restTemplate.exchange(...), the problem seems to be with matchers. As an example: the following won't work,
when(ecocashRestTemplate.exchange(Mockito.any()
, Mockito.eq(HttpMethod.GET)
, Mockito.any(HttpEntity.class)
, Mockito.<Class<UserTransaction>>any())
).thenReturn(new ResponseEntity<>(transaction, HttpStatus.OK));
but this one will actually work:
ResponseEntity<UserTransaction> variable = new ResponseEntity<>(transaction, HttpStatus.OK);
when(ecocashRestTemplate.exchange(Mockito.anyString()
, Mockito.eq(HttpMethod.GET)
, Mockito.any(HttpEntity.class)
, Mockito.<Class<UserTransaction>>any())
).thenReturn(new ResponseEntity<>(transaction, HttpStatus.OK));
NOTICE the Mockito.anyString() on the second block vs theMockito.any().
Let say you have an exchange call like below:
String url = "/zzz/{accountNumber}";
Optional<AccountResponse> accResponse = Optional.ofNullable(accountNumber)
.map(account -> {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "bearer 121212");
HttpEntity<Object> entity = new HttpEntity<>(headers);
ResponseEntity<AccountResponse> response = template.exchange(
url,
GET,
entity,
AccountResponse.class,
accountNumber
);
return response.getBody();
});
To mock this in your test case you can use mocitko as below:
when(restTemplate.exchange(
ArgumentMatchers.anyString(),
ArgumentMatchers.any(HttpMethod.class),
ArgumentMatchers.any(),
ArgumentMatchers.<Class<AccountResponse>>any(),
ArgumentMatchers.<ParameterizedTypeReference<List<Object>>>any())
)
When we are testing a Client which is communicating to some external system using restTemplate, as part of unit tests we need to verify the httpEntity, headers and parameters which we are sending.
ArgumentCaptor comes handy in these situation. So here is my example (working code)
#Mock
private RestTemplate restTemplate;
#InjectMocks
private MyClient client;
#Captor
ArgumentCaptor<HttpEntity<?>> httpEntityCaptor;
when(restTemplate.exchange(eq(expectedUrl), eq(HttpMethod.POST), Matchers.any(HttpEntity.class), eq(MyTargetResponse.class)).thenReturn(expectedResponse);
verify(restTemplate).exchange(eq(expectedUrl),eq(HttpMethod.POST), httpEntityCaptor.captor(),eq(MyTargetResponse.class));
HttpEntity<?> actualResponse = httpEntityCaptor.getValue();
HttpHeaders actualResponse.getHeaders();
assertEquals(headers.getFirst("Content-Type", "application/json")
Now assertions can be made based on your use case, as you have got the captured object which was sent.
You can use below non deprecated ArgumentMatchers
lenient().when(restTemplate.exchange(ArgumentMatchers.any(String.class),
ArgumentMatchers.eq(HttpMethod.GET),
ArgumentMatchers.any(),
ArgumentMatchers.eq(new ParameterizedTypeReference<List<ObjectA>>() {
})))
.thenReturn(responseEntity);
I implemented a small library that is quite useful. It provides a ClientHttpRequestFactory that can receive some context. By doing so, it allows to go through all client layers such as checking that query parameters are valued, headers set, and check that deserialization works well.
If you are using RestTemplateBuilder may be the usual thing wouldn't work. You need to add this in your test class along with when(condition).
#Before
public void setup() {
ReflectionTestUtils.setField(service, "restTemplate", restTemplate);
}
If anyone is still facing this issue, Captor annotation worked for me
#Captor
private ArgumentCaptor<Object> argumentCaptor;
Then I was able to mock the request by:
ResponseEntity<YourTestResponse> testEntity = new ResponseEntity<>(
getTestFactoryResponse(),
HttpStatus.OK);
when(mockRestTemplate.exchange((String) argumentCaptor.capture(),
(HttpMethod) argumentCaptor.capture(),
(HttpEntity<?>) argumentCaptor.capture(),
(Class<YourTestResponse.class>) any())
).thenReturn(testEntity);
For this specific exchange() case,
I found that easier just stub it instead, good old override:
var restTemplate = new RestTemplate() {
public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, #Nullable HttpEntity<?> requestEntity,
Class<T> responseType) throws RestClientException {
throw new HttpClientErrorException(HttpStatus.NOT_FOUND);
}
};
less mock stuff.. especially that api is always changing. eq(..) any().. etc.
you can check on arg inside of your stubbed exchange() before returning something or throwing exception.
--
I know that it is not the answer to that strict question. But same result. less code & easier to support.
With mockito-core-2.23.4
ResponseEntity<YOUR_CLASS> responseEntity = new ResponseEntity(YOUR_CLASS_OBJECT, HttpStatus.OK);
when(restTemplate.exchange(Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.<ParameterizedTypeReference<YOUR_CLASS>> any()))
.thenReturn(responseEntity);
Was get it working this way - Mockito.when(restTemplate.exchange((URI) any(), (HttpMethod) any(), (HttpEntity<?>) any(), (Class) any()))
.thenReturn(responseEntity);
However the above code does not work, it shows that responseEntitty is
null. How can I correct my test to properly mock
restTemplate.exchange?
It returns null because using:
#Mock
private RestTemplate restTemplate;
If the goal is to mock with MockRestServiceServer instead of Mockito, it should be:
#Autowired
private RestTemplate restTemplate;
or RestTemplate restTemplate = new RestTemplate()
when the same instance is provided to MockRestServiceServer and SomeService.
For example:
#Test
public void methodWithPostCallTest() throws URISyntaxException {
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(ExpectedCount.once(),
requestTo(new URI("post-method-url")))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body("response-body")
);
YourService yourService = new YourService(restTemplate);
String response = yourService.methodWhichExecutesPostCall();
mockServer.verify();
assertEquals("response-body", response);
}
Using MockRestServiceServer, it would return mock response for any RestTemplate method for a POST call - postForEntity, postForObject or exchange.
More details: Testing Client Applications
If your intention is test the service without care about the rest call, I will suggest to not use any annotation in your unit test to simplify the test.
So, my suggestion is refactor your service to receive the resttemplate using injection constructor. This will facilitate the test. Example:
#Service
class SomeService {
#AutoWired
SomeService(TestTemplateObjects restTemplateObjects) {
this.restTemplateObjects = restTemplateObjects;
}
}
The RestTemplate as component, to be injected and mocked after:
#Component
public class RestTemplateObjects {
private final RestTemplate restTemplate;
public RestTemplateObjects () {
this.restTemplate = new RestTemplate();
// you can add extra setup the restTemplate here, like errorHandler or converters
}
public RestTemplate getRestTemplate() {
return restTemplate;
}
}
And the test:
public void test() {
when(mockedRestTemplateObject.get).thenReturn(mockRestTemplate);
//mock restTemplate.exchange
when(mockRestTemplate.exchange(...)).thenReturn(mockedResponseEntity);
SomeService someService = new SomeService(mockedRestTemplateObject);
someService.getListofObjectsA();
}
In this way, you have direct access to mock the rest template by the SomeService constructor.
Related
I'm using Spring Boot 2.5.6 and JUnit 4.13.2. My task is to test the DELETE method
My REST controller:
#RestController
public class DomainEndpoint {
private final SomeService service;
#DeleteMapping("/domain/{id}")
public void delete(#PathVariable long id) {
service.delete(id);
}
}
My test:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#RunWith(SpringRunner.class)
public class DomainEndpointTest {
#Autowired
TestRestTemplate template;
#MockBean
SomeService service;
#Test
public void delete() {
String url = "/domain/123";
ResponseEntity<?> resp = template.exchange(url, HttpMethod.DELETE, new HttpEntity<>(""), String.class);
assertEquals(HttpStatus.NO_CONTENT, resp.getStatusCode());
}
}
As you can see, the only solution for testing the 'DELETE' method, which I found, is:
ResponseEntity<?> resp = template.exchange(url, HttpMethod.DELETE, new HttpEntity<>(""), String.class);
But params for body new HttpEntity<>("") and for return type String.class seem strange to me. Why should I use them? Can I do the same more straightway without passing unnecessary parameters?
On the other hand, TestRestTemplate template has a set of short and readable methods delete(). The problem with them - they return void and I can't check the response status code in this case.
The main question is how to test DELETE methods correctly?
Instead of passing in new HttpEntity<>("") HttpEntity has a special class variable you can use called HttpEntity.EMPTY for these situations. You can also use a return type of void.
ResponseEntity<Void> resp = template.exchange(url, HttpMethod.DELETE, HttpEntity.EMPTY, Void.class);
Two things you could improve:
Don't provide a request entity – it is marked as #Nullable
Use Void.class as return type to express that you don't expect any response body
ResponseEntity<Void> resp = restTemplate.exchange(url, HttpMethod.DELETE, null, Void.class);
Others have given different options but if you want to use the the delete method - it internally handles the http error by throwing runtime error as you can see here. You can check for HttpClientErrorException or HttpServerErrorException by using assertDoesNotThrow from JUnit5
In my opinion RestTemplate.delete method is the one to use, return type is void but status other than 2XX will throw an exception.
My current unit test using RestTemplate to reach a GET endpoint has a lot of hard-coded values. I would like to know if there's a better approach mocking the values and how to do it.
Currently it looks like this:
#RunWith(MockitoJUnitRunner.class)
public class CarServiceTest {
private final String uri = "https://cars.com/cars";
private final String token = "xxx";
private List<Car> carList;
private Cars testCars;
#Mock
private RestTemplate restTemplate;
#InjectMocks
private CarService carService;
#BeforeAll
void init() {
carList = new ArrayList<>(Arrays.asList(new Car("car1")));
testCars = new Cars(carList);
}
#Test
public void test_getCars() {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(token);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
Mockito.when(restTemplate.exchange(
RequestEntity.get(new URI(uri)).headers(headers).build(),
Cars.class))
.thenReturn(new ResponseEntity(testCars, HttpStatus.OK));
Cars returnedCars = carService.getCars();
// assertion
Assert.assertEquals(testCars, returnedCars);
// verify
verify(restTemplate).exchange(RequestEntity
.get(new URI(uri)).headers(headers).build(),
Cars.class);
verifyNoMoreInteractions(restTemplate);
}
}
I'm looking for suggestions on how to improve the initialization/mocking of the objects being used in the test. (I'm currently using real URL and token in the tests)
You can use the MockRestServiceServer.
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build();
server.expect(MockRestRequestMatchers.requestTo(uri))
.andExpect(requestTo(uri))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(new ObjectMapper()
.writeValueAsString(cars), MediaType.APPLICATION_JSON));
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(token);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
restTemplate.exchange(RequestEntity.get(new URI(uri)).headers(headers).build(), Cars.class))
server.verify();
You can setup the stub that you need and also verify that the calls you expected actually happened in a few lines of code and is much more clearer IMO.
I am trying to mock an exchange call from a rest template but for some reason i am getting a null response from the call rather than the response entity i have specified in my test. Note - before adding the injectMocks on my service interface the rest template was trying to make an actual call, when i added that in it makes a mock call but with a null result.
#ActiveProfiles("unit-test")
#RunWith(SpringJUnit4ClassRunner.class)
#Category({ UnitTests.class })
#SpringBootTest#Import({PropertiesTestConfiguration.class})
public class MyTest {
#Mock
OAuth2RestTemplate serviceRestTemplate;
#Autowired
#InjectMocks
ServiceInterface serviceInterface;
#Test
public void getServiceResponse_Success() {
ResponseEntity<String> mockResponseEntity = new ResponseEntity<String>(mockResponseBody, HttpStatus.OK);
String url = "https://unit_test_/XXX";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
Mockito.when(serviceRestTemplate.exchange( Matchers.anyObject(), Matchers.any(HttpMethod.class), Matchers.<HttpEntity> any(), Matchers.<Class<String>> any()) ).thenReturn(mockResponseEntity);
ServiceInterface.getClaimByClaimId(XXX);
}
}
In the method I am testing this returns null
responseEntity = serviceRestTemplate.exchange(uriBuilder.toUriString(),
method, requestEntity, String.class);
If you are using hamcrest matchers i will recommed to use is and isA methods to match the value or instance, this artical explains more about core matchers
Mockito.when(serviceRestTemplate.exchange(is(instanceOf(String.class)),
is(HttpMethod.GET),
is(HttpEntity.class),
isA(String.class)))
.thenReturn(mockResponseEntity);
I have the following implementation of class A, using spring boot.
A is an abstraction over restTemplate to make GET/POST/PUT RestAPI calls.
Tests are written using Mockito.
Class A {
#Bean
RestTemplate restTemplate;
public class A(RestTemplate restTemplate){
this.restTemplate = restTemplate;
}
public ResponseEntity perform(String endPoint, String requestBody, String auth, HttpMethod httpMethod){
...code to create parameters to pass to the exchange method
ResponseEntity responseEntity = restTemplate.exchange(url, httpMethod, requestEntity, String.class)
return responseEntity;
}
}
Unit Test With Mockito:
class ATest{
A a;
#Spy
RestTemplate restTemplate;
#Before
public void setUp() {
a = Mockito.spy(new A(restTemplate));
}
#Test
public void testHttpUtil(){
ResponseEntity responseEntity = new ResponseEntity(HttpStatus.OK);
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization" , "testAuth");
HttpEntity<String> requestEntity = new HttpEntity<String>("testPayload", headers);
Mockito.doReturn(responseEntity).when(restTemplate).exchange(Mockito.any(),Mockito.eq(HttpMethod.POST), Mockito.any(HttpEntity.class), Mockito.eq(String.class));
ResponseEntity responseEntity1 = a.perform("https://example.com/v1/testapi", "testpayload", "testauthinfo", MediaType.APPLICATION_JSON_VALUE, HttpMethod.POST );
Assert.assertNotNull(responseEntity1);
Mockito.verify(restTemplate, Mockito.atMost(1)).exchange(Mockito.any(),Mockito.eq(HttpMethod.POST), Mockito.any(HttpEntity.class), Mockito.eq(String.class));
}
}
My thought behind this implementation is to mock the restTemplate's exchange method, and return a response, upon calling A's perform method.
Right now null is being returned upon A's perform method.
Looks like I am doing something wrong. Can someone pls assist me on this?
To circumvent this, I mocked the RestTemplate and used
Mockito.verify(restTemplate, Mockito.atMost(1)).exchange(Mockito.any(),Mockito.eq(HttpMethod.POST), Mockito.any(HttpEntity.class), Mockito.eq(String.class));
to find out how many times restTemplate.exchange method has been called. Tests are passing now. But still want to know what's wrong with the implementation posted in the question
Update: On instantiating the restTemplate, the test passed. Did the following change to the test code.
#Spy
RestTemplate restTemplate = new RestTemplate();
JUnit test class :
public class TestingClass {
#Mock
private RestTemplate restTemplate;
#Mock
private HttpEntity entity;
#Mock
private ResponseEntity<Resource> responseEntity;
#Before
public void setup() {
MockitoHelper.initMocks(this);
}
#Test
public void getDataTest() {
ClassToTest c = new ClassToTest(restTemplate);
when(restTemplate.exchange("http://testing", HttpMethod.GET, entity, Resource.class)).thenReturn(responseEntity);
c.getData("http://testing");
}
}
Class being tested :
import org.jsoup.helper.Validate;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import java.io.InputStream;
import java.util.Optional;
public class ClassToTest {
private HttpHeaders headers;
private RestTemplate restTemplate;
public ClassToTest(RestTemplate restTemplate){
this.restTemplate = restTemplate;
headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
}
public Optional<InputStream> getData(final String mediaUrl) {
Validate.notEmpty(mediaUrl);
final String requestJson = "{}";
final HttpEntity<String> entity = new HttpEntity<>(requestJson, headers);
Optional inputStreamOptional = Optional.empty();
try {
final ResponseEntity<Resource> responseEntity = restTemplate.exchange(mediaUrl, HttpMethod.GET, entity, Resource.class);
System.out.println(responseEntity);
} catch (Exception exception) {
exception.printStackTrace();
}
return inputStreamOptional;
}
}
The result of System.out.println(responseEntity); is null .
Should responseEntity be set to it's mocked value and returned (instead of current behavior where null is returned) as is configured in : when(restTemplate.exchange("http://testing", HttpMethod.GET, entity, Resource.class)).thenReturn(responseEntity);
So when c.getData("http://testing");is invoked the mocked responseEntity is returned ?
Update use instead :
when(restTemplate.exchange(Matchers.eq("http://testing"), Matchers.eq(HttpMethod.GET), Matchers.isA(HttpEntity.class), Matchers.eq(Resource.class))).thenReturn(responseEntity);
It's most likely returning null because your parameter definition in when and the actual parametes differ. In your case it's very likely that your mocked entity and the HttpEntity you're creating in your code under test are neither the same nor equal. So you need to widen your expectations in the when-definition. You should use Matchers in your definition and the could use isA(HttpEntity.class) for your entity.
I think that you don't need to mock ResponseEntity, since ResponseEntity is not the one being injected. You mocked ResponseEntity and then never mocked any of its methods , that's why its null.
You need to mock RestTemplate and then mock its:
when(restTemplate.exchange("http://testing", HttpMethod.GET, entity, Resource.class)).thenReturn(responseEntity);
The problem is centered around this line in your test:
when(restTemplate.exchange("http://testing", HttpMethod.GET, entity, Resource.class)).thenReturn(responseEntity);
What this says is to return your mock responseEntity when you call restTemplate.exchange with these specific arguments. Notice that one of those arguments is your mock entity. However, in your ClassToTest, that mock entity is not what's passed into restTemplate.exchange. So when you run your test, you never hit the exact method signature expected by your stub.
Try this, instead:
when(restTemplate.exchange(eq("http://testing"), eq(HttpMethod.GET), any(HttpEntity.class), eq(Resource.class))).thenReturn(responseEntity);
This replaces the specific parameters with matchers, and will cause your stubbed method to trigger on any HttpEntity rather than the specific mock one.
Note that if you use any matchers at all, you have to use matchers for all the parameters, which is what the eq() method is for.