How to Mock REST API in unit testing? - java

I am using RestTemplate exchange HttpMethod.POST method to POST to an endpoint. In my test file I am testing for success of the POST method. However with my current tests I am getting 401 Unauthorized error when POST request is made. I need help to Mock the API while making POST request in test file
Here is my main file
#Component
public class DataTestRepo {
private final RestTemplate restTemplate;
private final String url;
private final AllBuilder headersBuilder;
public DataTestRepo(
#Qualifier(Oauth.BEAN_NAME) AllBuilder headersBuilder,
RestTemplate restTemplate, String url) {
this.headersBuilder = headersBuilder;
this.restTemplate = restTemplate;
this.url = url;
}
public ResponseEntity<String> postJson(Set<String> results) {
ResponseEntity<String> result = null;
try {
JSONObject jsonObject = new JSONObject(body);
HttpEntity<String> request = new HttpEntity<String>(jsonObject.toString(), null);
restTemplate.getMessageConverters().add(stringConvertor);
result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<>(request, getHttpHeaders()), String.class);
}
return result;
}
}
Here is my test file
#RunWith(MockitoJUnitRunner.class)
#TestPropertySource
public class DataTestRepoTest {
private static final String url = "http://localhost:8080/data/name";
#Mock
private DataTestRepo DataTestRepo;
RestTemplate restTemplate = new RestTemplate();
#Test
public void restTemplateHttpPost_success() throws URISyntaxException {
URI uri = new URI(url);
Set<String> mockData = Stream.of("A","B").collect(Collectors.toSet());
Map<String, String> body = new HashMap<>();
body.put("Name", "Aws");
JSONObject jsonObject = new JSONObject(body);
HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), null);
ResponseEntity<String> result = restTemplate.exchange(uri, HttpMethod.POST,
new HttpEntity<>(request, DataTestRepo.getHttpHeaders()), String.class);
Assert.assertEquals(201, result.getStatusCodeValue());
}
}

You are testing the logic inside DataTestRepo class, so you should not mock it.
RestTemplate is a dependency inside DataTestRepo, so this is exactly what you need to mock.
In general it should look like this inside your test:
#InjectMocks
private DataTestRepo DataTestRepo;
#Mock
RestTemplate restTemplate;
Also, you will have to provide a return value for your mocked dependency, like this:
Mockito.when(restTemplate.exchange(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(new ResponseEntity<>(yourExpectedDataHere, HttpStatus.OK));
enter code here
This is just a simple example. A good practice would be to check that the arguments passed to your mock equal to the expected ones. One way would be to replace ArgumentMatchers.any() with the real expected data. Another is to verify it separately, like this:
Mockito.verify(restTemplate, Mockito.times(1)).exchange(ArgumentsMatchers.eq(yourExpectedDataHere), ArgumentsMatchers.eq(yourExpectedDataHere), ArgumentsMatchers.eq(yourExpectedDataHere), ArgumentsMatchers.eq(yourExpectedDataHere));

This is a great read on this topic: https://reflectoring.io/spring-boot-web-controller-test/

Related

Mockito unit testing RestTemplate

I am using RestTemplate postForEntity method to post body to an endpoint. I need help with writing test case for my code using Mockito. The return type is void but it can be changed to Types or code if needed to test. I have referred many other documentation but they are very general, I tried using them but most did not work for me as the request and return type are different. . Any suggestions are appreciated. Thank you
Here is my Java class
public void postJson(Set<Type> Types){
try {
String oneString = String.join(",", Types);
Map<String, String> requestBody = new HashMap<>();
requestBody.put("type", oneString);
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<String>(jsonObject.toString(), null);
ResponseEntity result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<>(request, getHttpHeaders()), String.class);
}
}
}
You are testing the logic inside MyClass class, so you should not mock it. RestTemplate is a dependency inside MyClass, so this is exactly what you need to mock. In general it should look like this inside your test:
This is just a simple example. A good practice would be to check that the arguments passed to your mock equal to the expected ones. One way would be to replace Mockito.eq() with the real expected data. Another is to verify it separately, like this:
public ResponseEntity<String> postJson(Set<Type> Types){
try {
String oneString = String.join(",", Types);
Map<String, String> requestBody = new HashMap<>();
requestBody.put("type", oneString);
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<String>(jsonObject.toString(), null);
ResponseEntity result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<>(request, getHttpHeaders()), String.class);
}
}
return Types;
You can write test for above method as follows
#Mock
RestTemplate restTemplate;
private Poster poster;
HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), getHttpHeaders());
ResponseEntity<String> result = restTemplate.exchange(uri, HttpMethod.POST, request, String.class);
Mockito.verify(restTemplate, Mockito.times(1)).exchange(
Mockito.eq(uri),
Mockito.eq(HttpMethod.POST),
Mockito.eq(request),
Mockito.eq(String.class));
Assert.assertEquals(result, poster.postJson(mockData));
HttpHeaders getHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add(// whatever you need to add);
return headers;
}
Here is my solution to this problem, but it requires some changes.
First of all, you have to externalize the RestTemplate as a bean and add converters in its initialization. This way you will get rid of not covering those converters.
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
restTemplate.getMessageConverters().add(jsonConverter);
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
restTemplate.getMessageConverters().add(stringConverter);
return restTemplate;
}
Here's the new class that contains postJson method. As you can see, the url and restTemplate are injected through the constructor. This way we can test different cases.
public class Poster {
private RestTemplate restTemplate;
private String url;
public Poster(RestTemplate restTemplate, String url) {
this.restTemplate = restTemplate;
this.url = url;
}
public void postJson(Set<Type> types) {
try {
String oneString = types.stream().map(Type::toString).collect(Collectors.joining(","));
Map<String, String> requestBody = new HashMap<>();
requestBody.put("type", oneString);
requestBody.put("data", "aws");
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), null);
ResponseEntity<String> result = restTemplate
.postForEntity(url, new HttpEntity<>(request, getHttpHeaders()), String.class);
int code = result.getStatusCodeValue();
} catch (Exception ignored) {}
}
private HttpHeaders getHttpHeaders() {
return new HttpHeaders();
}
}
And here's the test class for that method.
class PosterTest {
#Mock
private RestTemplate restTemplate;
private String url;
private Poster poster;
#BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
this.url = "http://example.com/posturl";
this.poster = new Poster(restTemplate, url);
}
#Test
void postJson() {
// set input, I used TreeSet just to have a sorted set
// so that I can check the results against
Set<Type> types = new TreeSet<>(Comparator.comparing(Type::toString));
types.add(new Type("a"));
types.add(new Type("b"));
types.add(new Type("c"));
types.add(new Type("d"));
// response entity
ResponseEntity<String> response = ResponseEntity.ok("RESPONSE");
// mockito mock
Mockito.when(restTemplate.postForEntity(
ArgumentMatchers.eq(url),
ArgumentMatchers.any(HttpHeaders.class),
ArgumentMatchers.eq(String.class)
)).thenReturn(response);
// to check the results
Map<String, String> requestBody = new HashMap<>();
requestBody.put("type", "a,b,c,d");
requestBody.put("data", "aws");
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), null);
HttpEntity<HttpEntity<String>> httpEntity = new HttpEntity<>(request, new HttpHeaders());
// actual call
poster.postJson(types);
// verification
Mockito.verify(restTemplate, times(1)).postForEntity(
ArgumentMatchers.eq(url),
ArgumentMatchers.eq(httpEntity),
ArgumentMatchers.eq(String.class));
}
}
It is better to have the dependencies like RestTemplate or other ServiceClasses so that they can be mocked and tested easily.
A while back, I wrote about unit testing and test doubles. You can have a read as a starting point on how to approach unit testing.
Some of it's key take aways are:
Test Behaviour not Implementation. Tests that are independent of implementation details are easier to maintain. In most cases, tests should focus on testing your code’s public API, and your code’s implementation details shouldn’t need to be exposed to tests.
The size of the unit under test is discretionary but the smaller the unit, the better it is.
When talking about unit tests, a more quintessential distinction is whether the unit under test should be sociable or solitary.
A test double is an object that can stand in for a real object in a test, similar to how a stunt double stands in for an actor in a movie. They are test doubles not mocks. A mock is one of the test doubles. Different test doubles have different uses.
It's hard to write a whole test as a lot of information is missing. E.g. what Type is.
As you did not posted the name of your class I'm just name it MyClass for now.
Also I'm assuming that the RestTemplate is injected via the constructor like
MyClass(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
Following is a draft for a unit test using JUnit 5 and Mockito.
I would suggest mocking the RestTemplate. I have to admit that this way we will not cover to test the usage of MappingJackson2HttpMessageConverter and StringHttpMessageConverter in our test.
So a very raw draft could look like this
import java.util.ArrayList;
import java.util.Set;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.http.HttpEntity;
import org.springframework.web.client.RestTemplate;
class MyClassTest {
private RestTemplate restTemplate;
private MyClass myClass;
#BeforeEach
void setUp() {
restTemplate = Mockito.mock(RestTemplate.class);
myClass = new MyClass(restTemplate);
}
#Test
void callMethod() {
Set<Type> types = Set.of(/* one of your Types */);
String url = "http://someUrl";
String httpResult = "";
Mockito.when(restTemplate.getMessageConverters()).thenReturn(new ArrayList<>());
ArgumentCaptor<HttpEntity> request = ArgumentCaptor.forClass(HttpEntity.class);
Mockito.when(restTemplate.postForObject(url, request.capture(), String.class)).thenReturn(httpResult);
myClass.callMethod(types, url);
HttpEntity<String> actualHttpEntity = request.getValue();
Assert.assertEquals(actualHttpEntity.getBody(), "");
}
}

Mockito mock restTemplate not using returning mock value

Relevant Code Below:
ServiceCode:
#Override
public ResponseEntity<AppointmentResponse> createAppointment(AppointmentRequest partnerFulfillmentRequest) {
RestTemplate rt = null;
ResponseEntity<AppointmentResponse> response = null;
String uri = null;
HttpEntity<AppointmentRequest> httpEntity = null;
HttpHeaders headers = null;
try {
rt = new RestTemplate();
rt.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
rt.getMessageConverters().add(new StringHttpMessageConverter());
uri = new String(internalServiceUrl+"/"+APP_NAME_INTERNAL+"/appointment");
log.info("Calling internal service URL : "+uri);
headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
httpEntity = new HttpEntity<AppointmentRequest>(partnerFulfillmentRequest, headers);
response = rt.exchange(uri, HttpMethod.PUT, httpEntity, AppointmentResponse.class);
if (response != null)
{
log.info("Got response from internal servicec-->statusCode: "+response.getStatusCodeValue());
log.info("Got response from internal service--> Body "+response.getBody());
}
}catch(HttpClientErrorException hceEx) {
//hceEx.printStackTrace();
AppointmentResponse res = new AppointmentResponse();
return new ResponseEntity<AppointmentResponse>(mapResponse(hceEx.getResponseBodyAsString()), hceEx.getResponseHeaders(), hceEx.getStatusCode());
}catch(Exception e) {
e.printStackTrace();
AppointmentResponse res = new AppointmentResponse();
ResponseEntity<AppointmentResponse> wfmErrResponse = new ResponseEntity<AppointmentResponse>(res, HttpStatus.INTERNAL_SERVER_ERROR);
log.error("ERROR WHILE CALLING INTERNAL SERVICE");
log.error(uri);
log.error(e);
return wfmErrResponse;
}
return response;
}
Test Code:
#RunWith(MockitoJUnitRunner.class)
public class PartnerFulfillmentServiceImplTest {
#Mock
RestTemplate restTemplate;
#Mock
HttpHeaders httpHeaders;
#Mock
ResponseEntity responseEntity;
#InjectMocks
PartnerFulfillmentServiceImpl partnerFulfillmentService;
#Test
public void createAppointmentTest() {
Whitebox.setInternalState(partnerFulfillmentService, "internalServiceUrl", "http://localhost:8080");
AppointmentRequest appointmentRequest = new AppointmentRequest();
appointmentRequest.setPartnerName("CENTRICITY");
appointmentRequest.setTicketNumber("123ABC");
httpHeaders = new HttpHeaders();
httpHeaders.set("Content-type", "application/json");
responseEntity = new ResponseEntity<>(
"some response body",
HttpStatus.OK
);
when(restTemplate.exchange(Mockito.anyString(),
Mockito.<HttpMethod> any(),
Mockito.<HttpEntity<?>> any(),
Mockito.<Class<Object>> any()))
.thenReturn(responseEntity);
ResponseEntity<AppointmentResponse> response = partnerFulfillmentService.createAppointment(appointmentRequest);
Assert.assertEquals(response.getStatusCode(), HttpStatus.OK);
}
}
I'm getting
java.lang.AssertionError:
Expected :500
Actual :200
and understandably so because it is not actually calling running .thenReturn(responseEntity); logic. My million dollar question is, why? It should be returning the responseEntity value. I have all arguments for the exchange() to any() in hopes to trigger the condition as often as possible as I can always narrow the conditions at a different time. Am I not mocking my restTemplate correctly? That is my current suspicion as to what is going on. Any advice would help!
Thanks!
Like #JB Nizet pointed out, you are creating a new instance of RestTemplate inside your tested method. This means that the exchange method will be called from the new instance, and not a mock. You could implement it the way you did if the class that contains the method createAppointment had a dependency injection of RestTemplate.
What you want there, is to mock the constructor of the new instance of RestTemplate so that, when a new instance would be created, it will be substituted. Unfortunately, Mockito is not capable of mocking a constructor, so you should use PowerMockito for mocking constructors.
whenNew(RestTemplate.class).withNoArguments().thenReturn(restTemplate);
responseEntity = new ResponseEntity<>(
"some response body",
HttpStatus.OK
);
when(restTemplate.exchange(Mockito.anyString(),
Mockito.<HttpMethod> any(),
Mockito.<HttpEntity<?>> any(),
Mockito.<Class<Object>> any()))
.thenReturn(responseEntity);

Testing rest client in a abstract class directly, using Mockito

I have an abstract class in which I have a method called getInfo that uses restTemplate.exchange. I want to test method directly using Mockito without creating another concrete class to test this method. However, I am getting the null pointer exception when trying to do so.
When I used the same code to test the same rest client code in a concrete class, it works fine.
Below is the abstract class:
public abstract class classToBeTested{
#Autowired
private RestTemplate restTemplate;
protected Response methodToBeTested(){
String url = "https://example.com";
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<ClaimsResponse> response = restTemplate.exchange(url, HttpMethod.GET, entity, Response.class);
return response;
}
}
#RunWith(MockitoJUnitRunner.class)
public class Test{
#Mock
private RestTemplate restTemplate;
#Test
public void testMethod(){
String url = "https://example.com";
classToBeTested service = Mockito.mock(classToBeTested.class, Mockito.CALLS_REAL_METHODS);
Response res = new Response();
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<Response> response = new ResponseEntity<>(res, HttpStatus.OK);
Mockito.when(restTemplate.exchange(url, HttpMethod.GET, entity, Response.class)).thenReturn(response);
Response result = service.methodToBeTested();
Assert.assertEquals("should be equal", res, result);
}
}
This test should pass but I am getting null pointer exception on the line below:
ResponseEntity<ClaimsResponse> response = restTemplate.exchange(url, HttpMethod.GET, entity, Response.class)

How to write mockito junit for Resttemplate postForObject method

I am trying to post list of messages to the rest api. How to write mockito junit for the method postJSONData below:
public class PostDataService{
#Autowired
RestTemplate restTemplate;
#Autowired
private Environment env;
private HttpEntity<String> httpEntity;
private HttpHeaders httpHeaders;
private String resourceURL = null;
public PostDataService(){
httpHeaders = new HttpHeaders();
httpHeaders.set("Content-Type", "application/json");
}
public void postJSONData(List<String> data){
try
{
resourceURL = env.getProperty("baseURL") + env.getProperty("resourcePath");
httpEntity = new HttpEntity<String>(data.toString(), httpHeaders);
String response = restTemplate.postForObject(resourceURL, httpEntity, String.class);
}
catch (RestClientException e) {
LOGGER.info("ErrorMessage::" + e.getMessage());
LOGGER.info("ErrorCause::" + e.getCause());
}
}
}
Please help me how to write.
You can use Mockito to:
Create an instance of postData with mocked RestTemplate and Environment
Set expectations on these which allow the ``postJSONData` call to complete
Verify that the mocked RestTemplate is invoked correctly
The postJSONData method does not use the restTemplate.postForObject() response so the best you can do in terms of testing this method is to verify that restTemplate.postForObject() is invoked with the correct parameters.
Here's an example:
#RunWith(MockitoJUnitRunner.class)
public class PostDataTest {
#Mock
private RestTemplate restTemplate;
#Mock
private Environment env;
#InjectMocks
private PostData postData;
#Test
public void test_postJSONData() {
String baseUrl = "theBaseUrl";
String resourcePath = "aResourcePath";
Mockito.when(env.getProperty("baseURL")).thenReturn(baseUrl);
Mockito.when(env.getProperty("resourcePath")).thenReturn(resourcePath);
List<String> payload = new ArrayList<>();
postData.postJSONData(payload);
// it's unclear from your posted code what goes into the HttpEntity so
// this approach is lenient about its expectation
Mockito.verify(restTemplate).postForObject(
Mockito.eq(baseUrl + resourcePath),
Mockito.any(HttpEntity.class),
Mockito.eq(String.class)
);
// assuming that the HttpEntity is constructed from the payload passed
// into postJSONData then this approach is more specific
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
Mockito.verify(restTemplate).postForObject(
Mockito.eq(baseUrl + resourcePath),
Mockito.eq(new HttpEntity<>(payload.toString(), headers)),
Mockito.eq(String.class)
);
}
}
On a side note; postData is an unusual name for a class and the postJSONData method provided in your OP doesn't compile; it references meterReadings rather than data.
You can use wiremock to mock the server. It's a mocking framework specifically for this job.
Add following dependency to your pom.xml:
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.12.0</version>
</dependency>
Add following rule to your test:
#Rule
public WireMockRule wireMockRule = new WireMockRule(); // default port is 8080
Then you should define your baseUrl and resourcePath properties in application.properties (or elsewhere). Remember, the server will be running on localhost.
After that you should mock HTTP response for resourcePath:
stubFor(get(urlEqualTo(resourcePath))
.withHeader("Accept", equalTo("application/json"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody(content)));
Then you can execute postJSONData method:
postData.postJSONData();
And finally, you can verify if a request to the server was correct.
verify(postRequestedFor(urlMatching(resourcePath))
.withRequestBody(matching(expectedBody))
.withHeader("Content-Type", matching("application/json")));
Just mock postForObject correctly:
#ExtendWith(MockitoExtension.class)
public class YourServiceTest {
#Mock
RestTemplate template;
#InjectMocks
private final YourService srv = new YourService();
#Test
public void yourTest() {
when(template.postForObject(anyString(),any(Object.class),eq(String.class)))
.thenReturn("xxxxxxxxxxx");
assertEquals("xxxxxxxxxxx", srv.yourMethod());
}
}

How to send JSON as a Input parameter from one Microservice to another using RestTemplate in Spring Boot

I want to send JSON as an input from Microservice M1 to a Microservice M2.
M1 and M2 both are on different machines.
I am new to Spring Boot,
I found some code but I am unable to get it.
Please help.
make a class on both microservices or make a jar of that class and add to both microservices so that they both can access the same data.
Lets say the class is
class TestData{
private String name;
private String id;
// getters and setters
}
Now you can send data from M1 to M2 as following
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
TestData data = new TestData();
HttpEntity<?> entity = new HttpEntity<Object>(data,headers);
ResponseEntity<Object> responseEntity = restTemplate.exchange("url", HttpMethod.POST, entity, Object.class);
In Microservice M2 you can write a controller to get the data and process it as follows
#RequestMapping(value="/url",method=RequestMethod.POST)
public Object do(#RequestBody TestData data){
// do something
return //something
}
Let's Say Your Have MicroService1 which needs to send JSONObject => JsonObject to another MicroService2 which is on different Machine but on same network .
Sender Side:
RestTemplate restTemplate = new RestTemplate();
String jsonString = restTemplate.postForObject("http://10.177.7.128:8080/user/insertJsonObject",jsonObject,String.class);
Syntax for restTemplate.postForObject is:
ResponseType var1 = restTemplate.postForObject("network ip Address:portnumber/path",JSONObject,ResponseType)
To Know the URI go to System Preferences > Network
To Receive the object at the receiver Side
#RequestMapping(value="/user/insertJsonObject", method=RequestMethod.POST)
public String updateProductSold(#RequestBody JSONObject jsonObject) {
...Body
...
...
return responseStatus;
Here is the sample code
public class Test {
public static void main(String[] args) {
String jsonString = "{\"id\" : \"123\",\"name\" : \"Tom\",\"class\" : {\"subject\" : \"Math\",\"teacher\" : \"Jack\"}}";
RestTemplate restTemplate = new RestTemplate();
String url = "http://192.1168.1.190:8080/test" // url for second service
System.out.println(responserEntityValue(jsonString,restTemplate,url,HttpMethod.POST,String.class));
}
public ResponseEntity<String> responserEntityValue(final String body, final RestTemplate restTemplate,
final String uRL, final HttpMethod requestMethod, final Class<String> stringClass) {
HttpHeaders headers = new HttpHeaders();
// Set all headers
headers.add(DatabaseConstants.CONTENT_TYPE, "application/json");
HttpEntity<String> request = new HttpEntity<>(body, headers);
return restTemplate.exchange(uRL, requestMethod, request, stringClass);
}

Categories

Resources