How to mock RestTemplate in Springboot app - spring-boot

This question has already been asked. The accepted answer doesn't work for me. Here is my code:-
My service is here:
#Service
public class PlantService {
#Autowired
RestTemplate restTemplate;
static String url = "http://some_url_?Combined_Name=Oak";
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
public String getJson() {
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
return response.getBody();
}
}
My unit test
#RunWith(SpringRunner.class)
class PlantServiceTest {
private PlantService plantService;
#Mock
#Autowired
private RestTemplate restTemplate;
#Before
void setUp() {
MockitoAnnotations.initMocks(this);
plantService = new PlantService();
}
#Test
void testGetJsonString() {
// arrange
String expectedJson = "Some json string";
ResponseEntity mocResponse = mock(ResponseEntity.class);
// act
when(restTemplate.getForEntity("url", String.class)).thenReturn(mocResponse);
String actualJson = plantService.getJson();
// assert
assertSame(expectedJson, actualJson);
}
}
When I debug and step into the actual code. I can see restTemplate is null and throws java.lang.NullPointerException. So how do I unit test this code?

I have tried your code running on my machine.
Please find the test running test class
#RunWith(SpringRunner.class)
class PlantServiceTest {
#InjectMocks
private PlantService plantService;
#Mock
private RestTemplate restTemplate;
String url = "http://some_url_?Combined_Name=Oak";
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
void testGetJsonString() {
// arrange
String expectedJson = "Some json string";
ResponseEntity mocResponse = new ResponseEntity("Some json string", HttpStatus.OK);
// act
when(restTemplate.getForEntity(url, String.class)).thenReturn(mocResponse);
String actualJson = plantService.getJson();
// assert
assertSame(expectedJson, actualJson);
}
}

You can do the following:
Remove #RunWith annotation.
Annontate your test class with #RestClientTest from org.springframework.boot.test.autoconfigure.web.client.RestClientTest.
Use MockRestServiceServer from org.springframework.test.web.client.MockRestServiceServer.
Mock the response of the server when being called in the test method, example:
#RestClientTest
public class MyTest {
#Autowired
private MockRestServiceServer server;
public void test() {
// setup
String expected = "test_value";
server.expect(requestToUriTemplate("/myendpoint"))
.andRespond(withSuccess(myJsonResponse, MediaType.APPLICATION_JSON));
// act
String actual = myClient.fetch(myRequestDto);
// assert
assertThat(actual, equalTo(expected));
server.verify();
}
}
I'm using assertThat from hamcrest, you can use whatever you want to assert the correctness of the result.

You should use constructor injection in your PlantService. For instance:
public class PlantService {
RestTemplate restTemplate;
#Autowired
public PlantService(RestTemplate restTemplate){
this.restTemplate = restTemplate;
}
}
And on your test you can just do:
plantService = new PlantService(restTemplate);
^^
yourMock

Your problem is the plantService = new PlantService(); You never inject into this selft created instance.
Solution 1
I usually do it like this:
#InjectMocks
private PlantService plantService = new PlantService();
#Mock
private RestTemplate restTemplate;
Remove the setup method and run with Mockito instead of the SpringRunner.
Solution 2
If you need the SpringRunner you can do the following:
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
plantService = new PlantService();
ReflectionTestUtils.setField(plantService, "restTemplate", restTemplate);
}
As I've worked with JUnit 5 in the last years, I'm not sure about the SpringRunner. In JUnit 5 I can use both extensions (Spring and Mockito at the same time). Maybe this also worked in JUnit 4.

Related

How to write Junit test cases for this class

#DeleteMapping(value = "/{id}", produces = APPLICATION_JSON_VALUE)
public ResponseEntity<Void> delete(#PathVariable Long id) {
log.debug("Delete by id Logo : {}", id);
try {
Logo entr = new Logo();
entr.setId(id);
logoRepository.delete(entr);
return ResponseEntity.ok().build();
} catch (Exception x) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}
}
For testing controllers we could mock mvc. Inject mock of service layers inside the controller.
Since you have mock for your service you could mock the response that service should return for a transaction using Mockito.when statement.
A sample code will be like.
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = HelloController.class)
public class HelloWorldTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private HelloService service;
#Test
public void testShouldReturnMessage() throws Exception {
when(service.sayHello()).thenReturn("Hello World");
mockMvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.ALL))
.andExpect(MockMvcResultMatchers.status().is(200))
.andExpect(MockMvcResultMatchers.content().string("Hello World"))
.andExpect(MockMvcResultMatchers.header().string("Content-Type", "text/plain;charset=UTF-8"))
.andExpect(MockMvcResultMatchers.header().string("Content-Length", "11"));
}
}
Reference - https://www.nexsoftsys.com/articles/spring-boot-controller-unit-testing.html#:~:text=Unit%20testing%20Spring%20Boot%20controllers%20Technology%20Unit%20testing,is%20to%20validate%20each%20unit%20performs%20as%20designed.

#Mock RestTemplate not returning expected value

I am creating unit test for my service class:
#Service
#Slf4j
public class SsaOpeningService {
#Autowired
private MockDataInitializer mockDataInitializer;
#Qualifier("restHttpsTemplateClient")
#Autowired
private RestTemplate restTemplate;
#Value("${acc-opening-casa.open-ssa}")
private String openSSAaccountUrl;
public CompletableFuture<AccountData> openSsa(
ApplicationDto items,
HttpHeaders headers,
BigInteger cifNo) {
log.info("Initializing headers");
HeaderRequest headerRequest = new HeaderRequest();
HttpHeaders header = headerRequest.initHeader(headers);
CurrentAcctReqBody request = CurrentAcctReqBody.builder()
.cifNo(cifNo)
.currencyType(SGD_CURRENCY)
.acName1(items.getApplicationData().getPersonalDetail().getName())
.productType(SSA_PRODUCT_CODE)
.noOfAccountHolders(BigInteger.ONE)
.accountType(ACC_TYPE)
.transactionRefNo(mockDataInitializer.randomIntInString(9))
.build();
log.info("Setting up entity for calling SSA opening.....");
HttpEntity<CurrentAcctReqBody> entity = new HttpEntity<>(request, header);
CurrentAcctResBody result = null;
try {
result = restTemplate
.postForObject(openSSAaccountUrl, entity, CurrentAcctResBody.class);
} catch (Exception e) {
log.info(e.getMessage());
}
System.out.println(14527);
System.out.println(result);
if(result !=null && result.getError()==null) {
AccountData accountData = AccountData.builder().build();
BeanUtils.copyProperties(result.getRbkAccountDetail(), accountData);
return CompletableFuture.completedFuture(accountData);
}
return null;
}
}
My test class:
#SpringBootTest
#RunWith(SpringRunner.class)
class SsaOpeningServiceTest {
#InjectMocks private SsaOpeningService ssaOpeningService;
#Autowired private MockDataInitializer dataInitializer;
#Mock private MockDataInitializer mockDataInitializer;
private static HttpHeaders headers = new HttpHeaders();
private static HeaderRequest ekycHeaderRequest = new HeaderRequest();
#BeforeAll
public static void init() {
headers = ekycHeaderRequest.initHeader();
}
#Qualifier("restHttpsTemplateClient")
#Mock private RestTemplate restTemplate;
#Test
void createSsa() throws IOException {
CurrentAcctResBody result = JSONUtils
.getObjectFromJson(DCResourceLoader.getResourceAsString("casa/ssa-res.json"), CurrentAcctResBody.class);
ApplicationDto items = ApplicationDto.builder().build();
Application application = dataInitializer.initialize();
BeanUtils.copyProperties(application, items);
when(restTemplate.postForObject(
any(String.class),
eq(HttpEntity.class),
eq(CurrentAcctResBody.class)))
.thenReturn(result);
System.out.println(1452);
System.out.println(result);
when(mockDataInitializer.randomIntInString(any(Integer.class)))
.thenReturn(dataInitializer.randomIntInString(9));
assertThat(ssaOpeningService.openSsa(items, headers, any(BigInteger.class))).isNull();
}
}
I have mocked my RestTemplate to return me the result I want. Problem is, it is not giving me the expected result. I have printed the result in both test class and service class. But in the service class it is always giving me null. I tried to give the most generic parameter when mocking but still doesnt work. The rest is working fine when running unit test EXCEPT for this part. Need assist on this. Thanks all!
#RunWith(MockitoJUnitRunner.class)
class SsaOpeningServiceTest {
#InjectMocks
private SsaOpeningService ssaOpeningService;
#Mock
private MockDataInitializer mockDataInitializer;
#Mock
private RestTemplate restTemplate;
#Test
void createSsa() throws IOException {
// some code
Mockito.when(restTemplate.postForObject(
nullable(String.class),
any(HttpEntity.class),
eq(CurrentAcctResBody.class)))
.thenReturn(result);
// some code
}
}

how to create RestController in test directory for SpringBoot Application

Im currently writing integration test for SpringBoot Application .
It's functionality is to receive/send request from outside and forward/receive them to another application(APP_2). So there are two systems which needs to be mocked outside System and APP_2 .
HomeController
#Controller
public class HomeController {
#Autowired
ForwardController forwardController;
#RequestMapping("/")
public #ResponseBody
String greeting() {
return forwardController.processGET().toString();
}
}
ForwardController
#Service
public class ForwardController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment environment;
private ResponseEntity sendRequest(String url, HttpMethod method, HttpEntity requestEntity, Class responseType, Object... uriVariables) {
return restTemplate.exchange( url, method, requestEntity, responseType,uriVariables);
}
public ResponseEntity processGET()
{
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
HttpEntity<?> entity = new HttpEntity<>(headers);
String app_2_url = environment.getProperty(Constants.APP_2_URL);
ResponseEntity<String> response = sendRequest(app_2_url,HttpMethod.GET,entity,String.class);
return response;
}
}
APP_2_CONTROLLER
#Controller
public class App_2_Controller {
#RequestMapping("/app2Stub")
public #ResponseBody
String greeting() {
return "Hello End of world";
}
}
Test Class which simulates the external request behavior to the system:
HTTP_request_Test
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = Application.class)
public class HttpRequestTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private Environment environment;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/",
String.class)).contains("Hello End of world");
}
}
Here in this test class I'm overriding the properties by having two property file. So when we run test the request would be sent to App_2_Controller ( Mock in my project ) rather than the real App .
QUESTION :
Is there any way to have the APP_2_CONTROLLER inside the test folder ? This is because I don't want to expose the unwanted test endpoint in my Actual application .
Here in the above project , Im changing the URL with properties. Is there a better way to put a controller for the same URL. For simplicity sake lets assume, app_2 url is app.com:9000/serve
Spring already comes with a MockRestServiceServer, that makes this a lot easier so that you don't have to create your own dummy controllers (App_2_Controller). So in your case, you can remove that controller, and write a test like this for ForwardController:
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class ForwardControllerTest {
#Autowired
private RestTemplate restTemplate;
#Autowired
private ForwardController forwardController; // Your service
private MockRestServiceServer server;
#Before
public void setUp() {
server = MockRestServiceServer.bindTo(restTemplate).build();
}
#Test
public void processGet_returnsResponseFromAPI() {
server.expect(once(), requestTo("http://app.com:9000/serve"))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess("Hello End of world", MediaType.TEXT_PLAIN));
assertThat(forwardController.processGET().getBody()).isEqualTo("Hello End of world"));
}
}
Additionally, you can create a separate test for your actual controller (ForwardController is just a service), mock ForwardController and use MockMvc:
#RunWith(SpringRunner.class)
#WebMvcTest
public class HomeControllerTest {
#Autowired
private HomeController homeController;
#Autowired
private MockMvc mockMvc;
#MockBean
private ForwardController forwardController;
#Test
public void greeting_usesForwardController() {
when(forwardController.expectGET()).thenReturn("Hello End of world");
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello End of world")));
}
}
In this case, you'll end up with two tests:
One test to verify that RestTemplate is used to capture the proper response from your external REST API.
Another test to verify that HomeController just forwards whatever ForwardController responds.

Using #RestClientTest in spring boot test

I want to write a simple test using #RestClientTest for the component below (NOTE: I can do it without using #RestClientTest and mocking dependent beans which works fine.).
#Slf4j
#Component
#RequiredArgsConstructor
public class NotificationSender {
private final ApplicationSettings settings;
private final RestTemplate restTemplate;
public ResponseEntity<String> sendNotification(UserNotification userNotification)
throws URISyntaxException {
// Some modifications to request message as required
return restTemplate.exchange(new RequestEntity<>(userNotification, HttpMethod.POST, new URI(settings.getNotificationUrl())), String.class);
}
}
And the test;
#RunWith(SpringRunner.class)
#RestClientTest(NotificationSender.class)
#ActiveProfiles("local-test")
public class NotificationSenderTest {
#MockBean
private ApplicationSettings settings;
#Autowired
private MockRestServiceServer server;
#Autowired
private NotificationSender messageSender;
#Test
public void testSendNotification() throws Exception {
String url = "/test/notification";
UserNotification userNotification = buildDummyUserNotification();
when(settings.getNotificationUrl()).thenReturn(url);
this.server.expect(requestTo(url)).andRespond(withSuccess());
ResponseEntity<String> response = messageSender.sendNotification(userNotification );
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
private UserNotification buildDummyUserNotification() {
// Build and return a sample message
}
}
But i get error that No qualifying bean of type 'org.springframework.web.client.RestTemplate' available. Which is right of course as i havn't mocked it or used #ContextConfiguration to load it.
Isn't #RestClientTest configures a RestTemplate? or i have understood it wrong?
Found it! Since i was using a bean that has a RestTemplate injected directly, we have to add #AutoConfigureWebClient(registerRestTemplate = true) to the test which solves this.
This was in the javadoc of #RestClientTest which i seem to have ignored previously.
Test which succeeds;
#RunWith(SpringRunner.class)
#RestClientTest(NotificationSender.class)
#ActiveProfiles("local-test")
#AutoConfigureWebClient(registerRestTemplate = true)
public class NotificationSenderTest {
#MockBean
private ApplicationSettings settings;
#Autowired
private MockRestServiceServer server;
#Autowired
private NotificationSender messageSender;
#Test
public void testSendNotification() throws Exception {
String url = "/test/notification";
UserNotification userNotification = buildDummyUserNotification();
when(settings.getNotificationUrl()).thenReturn(url);
this.server.expect(requestTo(url)).andRespond(withSuccess());
ResponseEntity<String> response = messageSender.sendNotification(userNotification );
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
private UserNotification buildDummyUserNotification() {
// Build and return a sample message
}
}

RestTemplate reads String data with "\" content (Even with StringHttpMessageConverter)

I am working with Spring Framework 4.3.6.RELEASE
For testing purposes through RestTemplate how a consumer, if the HTTP DELETE method fails because the entity does not exist, I need return an error message in raw format or String. It because 'normally' DELETE does not require mandatorily neither Accept nor Content-Type. I don't want complicate the user setting the Accept either for XML or JSON
My #Test method fails with the following error
java.lang.AssertionError:
Expected: is "The entity does not exist"
but: was "\"The entity does not exist\""
Observe the \" part
In the server side, I have declared:
#Bean
public StringHttpMessageConverter stringHttpMessageConverter(){
StringHttpMessageConverter converter = new StringHttpMessageConverter();
converter.setDefaultCharset(StandardCharsets.UTF_8);
converter.setSupportedMediaTypes(Arrays.asList(new MediaType(MediaType.TEXT_PLAIN, StandardCharsets.UTF_8)));
return converter;
}
And is registered:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(httpMessageConverterConfig.mappingJackson2HttpMessageConverter());
converters.add(httpMessageConverterConfig.mappingJackson2XmlHttpMessageConverter());
converters.add(httpMessageConverterConfig.stringHttpMessageConverter());
}
With a #ControllerAdvice, working together with a method annotated with #ExceptionHandler, a ResponseEntity<Object> object is created with the following approach in some place to establish the Content-Type
if(httpServletRequest.getMethod().equals(HttpMethod.DELETE.toString())){
headers.setContentType(new MediaType(MediaType.TEXT_PLAIN, StandardCharsets.UTF_8));
}
When I print the error message in the server side it prints without \" part
The RestTemplate for testing purposes is created with:
RestTemplate restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
restTemplate.setMessageConverters(customConverters);
restTemplate.setErrorHandler(restTemplateResponseErrorHandler);
Therefore why the \" part arrives to RestTemplate?
How it can be fix it.? What missing configuration is need it?
Alpha
Test Class declaration
#Transactional
#WebAppConfiguration
#RunWith(Parameterized.class)
#ActiveProfiles(resolver=TestActiveProfilesResolver.class)
#ContextConfiguration(classes={RootApplicationContext.class, ServletApplicationContext.class})
#TestExecutionListeners(listeners={LoggingTestExecutionListener.class}, mergeMode=MergeMode.MERGE_WITH_DEFAULTS)
public class PersonaDeleteOneRestControllerClientBadRequestTest {
Some variables in that class:
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private RestTemplateClientSupport restTemplateClientSupport;
private MockMvc mockMvc;
private RestTemplate restTemplate;
private static RestTemplateResponseErrorHandler restTemplateResponseErrorHandler;
Some creations:
#BeforeClass
public static void setUp_(){
restTemplateResponseErrorHandler = new RestTemplateResponseErrorHandler();
}
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
restTemplate = restTemplateClientSupport.deleteOneBadRequest(mockMvc);
restTemplate.setErrorHandler(restTemplateResponseErrorHandler);
}
The RestTemplate part is:
#Component
public class RestTemplateClientSupport {
private static final Logger logger = LoggerFactory.getLogger(RestTemplateClientSupport.class.getSimpleName());
private final List<HttpMessageConverter<?>> customConverters;
#Autowired
public RestTemplateClientSupport(List<HttpMessageConverter<?>> customConverters){
this.customConverters = customConverters;
}
private RestTemplate createRestTemplateWithListHttpMessageConverter(MockMvc mockMvc){
RestTemplate restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
restTemplate.getMessageConverters().removeIf(converter -> {
if(converter.getClass().getSimpleName().equals(MappingJackson2XmlHttpMessageConverter.class.getSimpleName())){
logger.info("Removing: {}" , converter.getClass().getSimpleName());
return true;
}
else{
return false;
}
});
restTemplate.getMessageConverters().removeIf(converter -> {
if(converter.getClass().getSimpleName().equals(MappingJackson2HttpMessageConverter.class.getSimpleName())){
logger.info("Removing: {}" , converter.getClass().getSimpleName());
return true;
}
else{
return false;
}
});
restTemplate.getMessageConverters().removeIf(converter -> {
if(converter.getClass().getSimpleName().equals(StringHttpMessageConverter.class.getSimpleName())){
logger.info("Removing: {}" , converter.getClass().getSimpleName());
return true;
}
else{
return false;
}
});
restTemplate.getMessageConverters().addAll(customConverters);
return restTemplate;
Note: I am using restTemplate.getMessageConverters().removeIf(Predicate<>.. because I need remove these 3 default converters and add my own one created.

Resources