How to properly mock my RestTemplate unit test? - java

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.

Related

Spring Boot / Mockito: Mocking RestTemplate but Response Always Null

I'm having problems mocking the response object of my Test Class when using Mockito. I'm trying to test an exception, for this I need one of the attributes of the Class that returns from the POST request. I've successfully mocked the RestTemplate but my when().thenReturn() is not returning anything and I'm getting a null pointer exception at the "if" validation. If anyone could help me on this problem I would be very grateful.
Here is my Service Class:
#Service
public class CaptchaValidatorServiceImpl implements CaptchaValidatorService{
private static final String GOOGLE_CAPTCHA_ENDPOINT = "someEndpoint";
private String stage;
private String captchaSecret;
private RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
#Override
public void checkToken(String token) throws Exception{
MultiValueMap<String,String> requestMap = new LinkedValueMap<>();
requestMap.add("secret", captchaSecret);
requestMap.add("response", token);
try{
CaptchaResponse response = restTemplate.postForObject(GOOGLE_CAPTCHA_ENDPOINT,
requestMap, CaptchaResponse.class);
if(!response.getSuccess()){
throw new InvalidCaptchaTokenException("Invalid Token");
}
} catch (ResourceAccessException e){
throw new CaptchaValidationNotPossible("No Response from Server");
}
}
private SimpleClientHttpRequestFactory getClientHttpRequestFactory(){
...
}
}
And here is my Test Class:
#SpringBootTest
public class CaptchaValidatorTest{
#Mock
private RestTemplate restTemplate;
#InjectMocks
#Spy
private CaptchaValidatorServiceImpl captchaValidatorService;
private CaptchaResponse captchaResponse = mock(CaptchaResponse.class);
#Test
public void shouldThrowInvalidTokenException() {
captchaResponse.setSuccess(false);
Mockito.when(restTemplate.postForObject(Mockito.anyString(),
ArgumentMatchers.any(Class.class), ArgumentMatchers.any(Class.class)))
.thenReturn(captchaResponse);
Exception exception = assertThrows(InvalidCaptchaTokenException.class, () ->
captchaValidatorService.checkToken("test"));
assertEquals("Invalid Token", exception.getMessage());
}
}
In my opinion it could be a problem with ArgumentMatchers.
Method postForObject require parameters as String, MultiValueMap(or parent) and Class, but you set in Mockito.when: anyString() (correct), any(Class.class) (but MultiValueMap is passed - probably incorrect) and any(Class.class) (correct).
Try use:
Mockito.when(restTemplate.postForObject(ArgumentMatchers.any(String.class),
ArgumentMatchers.any(MultiValueMap.class), ArgumentMatchers.any(Class.class)))
.thenReturn(captchaResponse);
EDIT:
It seems to me that the CaptchaResponse in the test is unnecessarily a mock:
private CaptchaResponse captchaResponse = mock(CaptchaResponse.class);
but if You want this in that way, I think u need to replace:
captchaResponse.setSuccess(false);
to something like:
Mockito.when(captchaResponse.getSuccess()).thenReturn(false);

Mockito - returning null

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();

How do I mock a REST template exchange?

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.

Do we have to create new instance of rest template in each method of integration test cases?

public void Method1() {
restTemplate = new RestTemplate();
}
public void Method2() {
restTemplate = new RestTemplate();
}
public void Method50() {
restTemplate = new RestTemplate();
}
....
Cant we create a mock of restTemplate and reuse it in all methods ?
Yes. You can just create a single instance and re-use it in all the cases. If you use a real RestTemplate in an integration test (say hitting an in-memory service or similar) this will be safe too as RestTemplate is thread-safe.

java.lang.AssertionError: Content type not set - Spring Controller Junit Tests

I am trying to do some unit testing on my controllers. No matter what I do all controller tests return
java.lang.AssertionError: Content type not set
I am testing that the methods return json and xml data.
Here is an example of the controller:
#Controller
#RequestMapping("/mypath")
public class MyController {
#Autowired
MyService myService;
#RequestMapping(value="/schema", method = RequestMethod.GET)
public ResponseEntity<MyObject> getSchema(HttpServletRequest request) {
return new ResponseEntity<MyObject>(new MyObject(), HttpStatus.OK);
}
}
The unit test is set up like this:
public class ControllerTest() {
private static final String path = "/mypath/schema";
private static final String jsonPath = "$.myObject.val";
private static final String defaultVal = "HELLO";
MockMvc mockMvc;
#InjectMocks
MyController controller;
#Mock
MyService myService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = standaloneSetup(controller)
.setMessageConverters(new MappingJackson2HttpMessageConverter(),
new Jaxb2RootElementHttpMessageConverter()).build();
when(myService.getInfo(any(String.class))).thenReturn(information);
when(myService.getInfo(any(String.class), any(Date.class))).thenReturn(informationOld);
}
#Test
public void pathReturnsJsonData() throws Exception {
mockMvc.perform(get(path).contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath(jsonPath).value(defaultVal));
}
}
I am using:
Spring 4.0.2
Junit 4.11
Gradle 1.12
I have seen the SO question Similiar Question but no matter what combination of contentType and expect in my unit test I get the same result.
Any help would be much appreciated.
Thanks
Your solution depends on what kinds of annotation you want to use in your project.
You can add #ResponseBody to your getSchema method in Controller
Or, maybe adding produces attribute in your #RequestMapping can solve it too.
#RequestMapping(value="/schema",
method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE} )
Final choice, add headers to your ResponseEntity (which is one of the main objective of using this class)
//...
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json; charset=utf-8");
return new ResponseEntity<MyObject>(new MyObject(), headers, HttpStatus.OK);
Edit : I've just seen you want Json AND Xml Data, so the better choice would be the produces attribute:
#RequestMapping(value="/schema",
method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE} )
You need to add
#RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE,
method = RequestMethod.GET
value = "/schema")
And <mvc:annotation-driven />in your xml config or #EnableWebMvc

Categories