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.
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();
In spring boot application (springBootVersion = '2.1.1.RELEASE'), I have two ReST endpoints. One returns object, and second returns list of object. I try to write test for this endpoints with MockMvc.
Test for single object, works as expected. Tests for list of objects returns 406 instead of 200. Below is my code, controller + tests for it.
Single object ReST:
#ResponseBody
#RequestMapping(value = {"/example", "/example.html"})
public ResponseEntity<SampleObject> example() {
SampleObject sampleObj = generateSampleObject();
return new ResponseEntity<>(sampleObj, HttpStatus.OK);
}
And test:
#Test
public void shouldCheckExamplePage() throws Exception {
MvcResult mvcResult = this.mockMvc.perform(get("/example")
.accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_UTF8_VALUE)))
.andExpect(status().isOk()).andReturn();
}
ReST endpoint for the list:
#ResponseBody
#RequestMapping(value = {"/", "/index.html"})
public ResponseEntity<List<SampleObject>> all() {
List<SampleObject> sampleObjectList = generateListOfSampleObject();
return new ResponseEntity<>(sampleObjectList, HttpStatus.OK);
}
And test:
#Test
public void shouldCheckHomePage() throws Exception {
MvcResult mvcResult = this.mockMvc.perform(get("/")
.accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_UTF8_VALUE)))
.andExpect(status().isOk()).andReturn();
}
What is a difference? Why for one object test works, but not for list?
Added:
when I start app, in browser all works fine, both endpoints.
Lets examine the code:
HTTP 406 shows that your backend didn't return the response client requests for.
Your client(your tests) requests for APPLICATION_JSON_UTF8_VALUE
APPLICATION_JSON_UTF8_VALUE means content type is JSON and it is encoded in the UTF-8 character encoding
A possible reason may be in your list response you have some objects that are not encoded in UTF-8
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.