problem mocking restTemplate with mockMvc - java

I need to write a unit test for a rest controller endpoint that calls a service which calls RestTemplate exchange()
#RestController
public class MyController {
#Autowired
Myservice myservice;
#GetMapping("todo")
public ResponseEntity<String> getTodo() {
String todoJson = myservice.getTodo();
return ResponseEntity.ok(todoJson);
}
}
Here's my service class
#Service
public class Myservice {
public String getTodo() {
ResponseEntity<Todo> response = restTemplate.exchange("https://jsonplaceholder.typicode.com/todos/1", HttpMethod.GET, null, Todo.class);
Todo todo = response.getBody();
return objectMapper.writeValueAsString(todo);
}
}
And test case
#ExtendWith(SpringExtension.class)
class MyControllerTestJunit5 {
private MockMvc mockMvc;
#Mock
private RestTemplate restTemplate;
#InjectMocks
MyController myController;
#Mock
Myservice myservice;
#BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(myController).build();
}
#Test
public void mockResttemplate() throws Exception {
Todo todosample = new Todo(5,6,"myfield", true);
ResponseEntity<Todo> responseEntity = ResponseEntity.ok(todosample);
when(restTemplate.exchange(
ArgumentMatchers.anyString(),
any(HttpMethod.class),
ArgumentMatchers.any(),
ArgumentMatchers.eq(Todo.class)))
.thenReturn(responseEntity);
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/todo"))
.andExpect(status().isOk())
.andReturn();
String myresult = result.getResponse().getContentAsString();
System.out.println("response: " + myresult);
}
printing the response at the end shows that the response is empty. How can I get this to work?

I assume that you expect to get the JSON representation of todosample object. The problem in the test is that you are mocking Myservice by annotating it with #Mock. That mocked service is used by MyController because it's annotated with #InjectMocks. As a result, when you make a call to the /todo endpoint the controller calls myservice.getTodo() which returns null as the Myservice mock is not configured to return anything else. After that, the null value gets propagated to the ResponseEntity body which materializes to the empty OK response in your test.
I believe a better approach for this kind of test will be to just mock Myservice and configure it properly. The first part is done already as I mentioned. The configuration is easy. You should just replace restTemplate.exchange call configuration with the myservice.getTodo() one. Something like this should work:
#Test
public void mockResttemplate() throws Exception {
String todosampleJson = "{}"; // just empty json
when(myservice.getTodo()).thenReturn(todosampleJson);
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/todo"))
.andExpect(status().isOk())
.andReturn();
String myresult = result.getResponse().getContentAsString();
System.out.println("response: " + myresult); // will print "{}" now
}
And then you can have a separate test for Myservice where you have to mock restTemplate.exchange call.
Of course technically, you can still get away with mocking the restTemplate in your MyController test but then you have to make sure you instantiate the real Myservice instead of the mocked version of it. However, in this case you would expose the implementation details of Myservice to the MyController class, which kinda contradicts the MVC pattern.

Related

JUnit | MockServer | RestTemplate

I have one service called PostService, which has instance variable called connectionManager Autowired.
Inside this connectionManager there is a one instance variable called restTemplate Autowired.
And in the configuration java file, object of RestTemplate is created with some logic.
When I write a test case around it, and create a MockRestServiceServer with an expected URL and post method, and in return expect a response with some body. and when I execute a test case I don't get mocked response from this mockedRestServiceServer.
As much, I can sense, this is because during test execution, a real object of rest template is created and mock server is not used to send mocked response.
can someone help me, how to overcome from this ?
class PostServie {
#Autowired
private ConnectionManager connectionManager;
public void postMessage(String msg) {
// some logic
}
}
#Component
class ConnectionManager {
#Autowired
private RestTemplate restTemplate;
public String getToken(){
ResponseEntity<String> response = this.restTemplate.postForEntity(url, request, String.class);
//returns response.body() in string format
}
}
#Configuration
class Configuration {
#Bean
public RestTemplate getRestTemplate(){
// some logic and returns object of RestTemplate
}
}
#RunWith(SpringRunner.class)
class PostServiceTest {
#Autowired
private PostMessageService postMessageService;
#Resource(name="authServerRestTemplet")
private RestTemplate authServerRestTemplet;
private MockRestServiceServer mockedAuthServerRestTemplet;
private final String requestToAuthServer ="grant_type=client_credentials&client_id=ceapiClientId";
#Before
public void setUp() {
mockedAuthServerRestTemplet =
MockRestServiceServer.createServer(authServerRestTemplet);
}
#Test
public void postServeiceSuccess () {
mockedAuthServerRestTemplet.expect(requestTo(ACCESS_TOKEN_URI)).andExpect(content().string(requestToAuthServer)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess("{abc : 'abc'}", MediaType.APPLICATION_JSON));
postMessageService.postMessage(jsonMessage);
}
}
}
You're creating an instance of RestTemplate but not using it. What is MockRestServiceServer doing?
I can also see a wrong utilisation of #Resource and #Autowired, which you shouldn't specify in your test. Try use #MockBean.
Autowiring works so that the resource you create in your test is injected in the real class, not the other way round.
I'll just add some sample code here, which might help you achieve your goal.
#RunWith(SpringRunner.class)
#WebMvcTest
public abstract class AbstractControllerTest {
#Autowired
protected MockMvc mockMvc;
#MockBean
protected Service service;
#Before
public void setUp() {
Mockito.reset(service);
}
}
and then your test
public class YourTest extends AbstractControllerTest {
#Test
public void shouldDoSomething() throws Exception {
// given
List<someStuff> stuff = new ArrayList<>();
stuff.add(new Whatever("content", "John Smith");
// when - service is in the abstract controller!
when(service.getSomething()).thenReturn(stuff);
// then
mockMvc.perform(get("/posts/1/whatever").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].content", is("content")))
.andExpect(jsonPath("$[0].author", is("John Smith"))));
}

JAVA - Getting empty MockHttpServletResponse: body..However it's 200

I have written unit test cases for a spring rest controller but I'm getting blank response body.
Below is my controller class
#RestController
#RequestMapping("/v2")
public class controller {
#Autowired
Service service;
#RequestMapping(value="/health",method=RequestMethod.POST)
public String postRequest(#RequestHeader #NotNull#NotBlank String
transID) {
return service.getTest(transID); }
}
Below is my service class
#Component
public class Service {
public String getTest(#NotNull String transid) {
return "Hello World Test";
} }
Below is my Unit Test class.I have written the unit test case for this method using mockito
class UnitTest {
#InjectMocks
controller controllerUse;
#Mock
Service service;
#Autowired
MockMvc mockMvc;
#BeforeEach
public void test() {
MockitoAnnotations.initMocks(this);
mockMvc=MockMvcBuilders.standaloneSetup(controllerUse).build();
}
#Test
public void confirmTest() throws Exception {
mockMvc.perform(post("/v2/health")
.header("transID","ABC"))
.andExpect(status().isOk())
.andDo(print())
.andReturn(); }
}
The OutPut:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
I am getting response code as 200. but getting blank body.what am i missing ? is this the standard way of writing unit test cases for spring rest controller ?
Since you are injecting mock bean service, you need to mock method with your expected response.
Here is sample code using Junit 5 and Spring Boot Test artifact but you can achieve same using standard spring test module as well.
#WebMvcTest(SampleController.class)
class SampleControllerTest {
#MockBean
Service service;
#Autowired
private MockMvc mockMvc;
#Test
public void confirmTest() throws Exception {
when(service.getTest("ABC")).thenReturn("Hello World");
mockMvc.perform(post("/v2/health")
.header("transID", "ABC"))
.andExpect(status().isOk())
.andDo(print())
.andReturn();
}
}

Why Spring Boot controller test returning 404, but controller found in context?

I've got a REST controller I'm trying to test, but when attempting to POST to it, I get a 404. The test is JUnit 5 and Spring Boot 2.1.5. If I run the application, I can hit the controller via Postman. I've run it in debug mode and verified that myController is not null and has the mocked services injected into it. What am I missing here? spring-boot-starter-test is a dependency, and junit4 is an exclusion.
#RestController
#Slf4j
#RequestMapping(path = /integrations,
produces = "application/json")
public class MyController {
private MyService myService;
private MyValidationService myValidationService;
public MyController(MySerivce service, MyValidationService myValidationService) {
this.myService = service;
this.myValidationService = myValidationService;
}
#PostMapping(path = "/users", produces = "application/json", consumes =
"application/json")
public ResponseEntity<User> getUserList(#Valid #RequestBody final RequestPayload
requestPayload, #RequestHeader Map<String, String> headers) throws MyException {
// check the credentials and permission
Map<String, String> credInfo = myValidationService.validateHeaders(headers);
// process payload
return myService.retrieveUsers(requestPayload);
}
}
Test is as follows:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
class MyControllerTest {
#MockBean
private MyService myService;
#MockBean
private MyValidationService myValidationService;
#Autowired
private MockMvc mockMvc;
#Autowired
MyController myController;
#Test
public void contextLoads() throws Exception {
Assert.assertNotNull(myController);
}
#Test
void getUserList() throws Exception {
List<User> users = returnUserList();
HttpEntity<RequestPayload> requestHttpEntity = new HttpEntity<>(returnPayload(), null);
when(myService.retrieveUsers(any(RequestPayload.class))).thenReturn(users);
mockMvc.perform(post("/integrations/users")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(returnPayload()))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().is2xxSuccessful());
}
}
The response I get is:
MockHttpServletResponse:
Status = 404
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
I appreciate the answers, but I discovered what the problem was - and it was a bonehead one!
It turns out that I included the context path in the mockMvc call when that value is already provided in the application.properties file! So instead of a URI of /integrations/users, the real URI being used was /integrations/integrations/users, which unsurprisingly does not exist!
Thanks to all and sorry for not taking my eyes in my hands and looking more closely.
You can use a slice test annotation instead. For testing a controller you can use #WebMvcTest.
Your setup would look like this:
#SpringBootTest(value = MyController.class)
#ExtendWith(SpringExtension.class)
class MyControllerTest {
You can still #Autowired the MockMvc. Also there is no need to autowire your controller in the test.

Junit MockMvc perform POST with path variable in URL return 404 not found

I have a SpringBoot application with this method in the controller to create an user in the database. The controller is working fine in Postman.
#RestController
#RequestMapping("/v1")
public class UserController {
#PostMapping(value = "/user/{id}")
public void createUser(#PathVariable Integer id, #Valid #RequestBody User request,
BindingResult bindingResult) throws Exception {
if (bindingResult.hasErrors()) {
throw new RequestValidationException(VALIDATION_ERRORS, bindingResult.getFieldErrors());
}
userService.createUser(id, request), HttpStatus.CREATED);
}
Now I have a junit test case to test this method and I am getting a 404
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApp.class)
public class UserTest {
private MockMvc mockMvc;
final String CREATE_USER_URL = "/v1/user/" + "10";
private final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
#Test
public void testCreateUser() throws Exception {
mockMvc.perform(post(CREATE_USER_URL)
// doesn't work either if I put "/v1/user/10" or post("/v1/user/{id}", 10) here
.content(TestUtils.toJson(request, false))
.contentType(contentType))
.andDo(print())
.andExpect(status().isCreated())
.andReturn();
}
But in the log, I was able to see the correct url:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /v1/user/10
Parameters = {}
Can someone please let me know why I am getting a 404 NOT Found? Thanks.
From docs you need #AutoConfigureMockMvc on class and #Autowire MockMvc
Another useful approach is to not start the server at all, but test only the layer below that, where Spring handles the incoming HTTP request and hands it off to your controller. That way, almost the full stack is used, and your code will be called exactly the same way as if it was processing a real HTTP request, but without the cost of starting the server. To do that we will use Spring’s MockMvc, and we can ask for that to be injected for us by using the #AutoConfigureMockMvc annotation on the test case:
Code :
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class UserTest {
#Autowire
private MockMvc mockMvc;
final String CREATE_USER_URL = "/v1/user/" + "10";
private final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
#Test
public void testCreateUser() throws Exception {
mockMvc.perform(post(CREATE_USER_URL)
// doesn't work either if I put "/v1/user/10" or post("/v1/user/{id}", 10) here
.content(TestUtils.toJson(request, false))
.contentType(contentType))
.andDo(print())
.andExpect(status().isCreated())
.andReturn();
}
}
If want to Test your real springboot url Test (End to end Test)
u can use rest-assured or resttemplte
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Application.class)
#TestPropertySource(value={"classpath:application.properties"})
#SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
public class SpringRestControllerTest {
#Value("${server.port}")
int port;
#Test
public void getDataTest() {
get("/api/tdd/responseData").then().assertThat().body("data", equalTo("responseData"));
}
#Before
public void setBaseUri () {
RestAssured.port = port;
RestAssured.baseURI = "http://localhost"; // replace as appropriate
}
}
https://dzone.com/articles/test-driven-development-with-spring-boot-rest-api

Inject mock into Spring MockMvc WebApplicationContext

I'm working to test (via JUnit4 and Spring MockMvc) a REST service adapter using Spring-boot. The adapter simply passes along requests made to it, to another REST service (using a custom RestTemplate) and appends additional data to the responses.
I'd like to run MockMvc tests to perform controller integration tests, but want to override the RestTemplate in the controller with a mock to allow me to predefine the 3rd party REST response and prevent it from being hit during each test. I've been able to accomplish this by instantiating a MockMvcBuilders.standAloneSetup() and passing it the controller to be tested with the mock injected as listed in this post (and my setup below), however I am not able to do the same using MockMvcBuilders.webAppContextSetup().
I've been through a few other posts, none of which answer the question as to how this might be accomplished. I would like to use the actual Spring application context for the tests instead of a standalone to prevent any gaps as the application is likely to grow.
EDIT: I am using Mockito as my mocking framework and am trying to inject one of its mocks into the context. If this isn't necessary, all the better.
Controller:
#RestController
#RequestMapping(Constants.REQUEST_MAPPING_PATH)
public class Controller{
#Autowired
private DataProvider dp;
#Autowired
private RestTemplate template;
#RequestMapping(value = Constants.REQUEST_MAPPING_RESOURCE, method = RequestMethod.GET)
public Response getResponse(
#RequestParam(required = true) String data,
#RequestParam(required = false, defaultValue = "80") String minScore
) throws Exception {
Response resp = new Response();
// Set the request params from the client request
Map<String, String> parameters = new HashMap<String, String>();
parameters.put(Constants.PARAM_DATA, data);
parameters.put(Constants.PARAM_FORMAT, Constants.PARAMS_FORMAT.JSON);
resp = template.getForObject(Constants.RESTDATAPROVIDER_URL, Response.class, parameters);
if(resp.getError() == null){
resp.filterScoreLessThan(new BigDecimal(minScore));
new DataHandler(dp).populateData(resp.getData());
}
return resp;
}
}
Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringApplicationConfiguration(classes = MainSpringBootAdapter.class)
#TestPropertySource("/application-junit.properties")
public class WacControllerTest {
private static String controllerURL = Constants.REQUEST_MAPPING_PATH + Constants.REQUEST_MAPPING_RESOURCE + compressedParams_all;
private static String compressedParams_all = "?data={data}&minScore={minScore}";
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#InjectMocks
private Controller Controller;
#Mock
private RestTemplate rt;
#Value("${file}")
private String file;
#Spy
private DataProvider dp;
#Before
public void setup() throws Exception {
dp = new DataProvider(file);
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void testGetResponse() throws Exception {
String[] strings = {"requestData", "100"};
Mockito.when(
rt.getForObject(Mockito.<String> any(), Mockito.<Class<Object>> any(), Mockito.<Map<String, ?>> any()))
.thenReturn(populateTestResponse());
mockMvc.perform(get(controllerURL, strings)
.accept(Constants.APPLICATION_JSON_UTF8))
.andDo(MockMvcResultHandlers.print());
Mockito.verify(rt, Mockito.times(1)).getForObject(Mockito.<String> any(), Mockito.<Class<?>> any(), Mockito.<Map<String, ?>> any());
}
private Response populateTestResponse() {
Response resp = new Response();
resp.setScore(new BigDecimal(100));
resp.setData("Some Data");
return resp;
}
}
Spring's MockRestServiceServer is exactly what you're looking for.
Short description from javadoc of the class:
Main entry point for client-side REST testing. Used for tests that involve direct or indirect (through client code) use of the RestTemplate. Provides a way to set up fine-grained expectations on the requests that will be performed through the RestTemplate and a way to define the responses to send back removing the need for an actual running server.
Try to set up your test like this:
#WebAppConfiguration
#ContextConfiguration(classes = {YourSpringConfig.class})
#RunWith(SpringJUnit4ClassRunner.class)
public class ExampleResourceTest {
private MockMvc mockMvc;
private MockRestServiceServer mockRestServiceServer;
#Autowired
private WebApplicationContext wac;
#Autowired
private RestOperations restOperations;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
mockRestServiceServer = MockRestServiceServer.createServer((RestTemplate) restOperations);
}
#Test
public void testMyApiCall() throws Exception {
// Following line verifies that our code behind /api/my/endpoint made a REST PUT
// with expected parameters to remote service successfully
expectRestCallSuccess();
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/my/endpoint"))
.andExpect(status().isOk());
}
private void expectRestCallSuccess() {
mockRestServiceServer.expect(
requestTo("http://remote.rest.service/api/resource"))
.andExpect(method(PUT))
.andRespond(withSuccess("{\"message\": \"hello\"}", APPLICATION_JSON));
}
}
Here's another solution. Simply put, it just creates a new RestTemplate bean and overrides the one already registered.
So while it performs produces the same functionality as #mzc answer, it allows me to use Mockito to craft the response and verification matchers a bit easier.
Not that it's more than a couple lines of code, but it also prevents from having to add additional code to convert from the Response object to a string for the above mockRestServiceServer.expect().andRespond(<String>) method's arg.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringApplicationConfiguration(classes = MainSpringBootAdapter.class)
#TestPropertySource("/application-junit.properties")
public class WacControllerTest {
private static String Controller_URL = Constants.REQUEST_MAPPING_PATH + Constants.REQUEST_MAPPING_RESOURCE + compressedParams_all;
#Configuration
static class Config {
#Bean
#Primary
public RestTemplate restTemplateMock() {
return Mockito.mock(RestTemplate.class);
}
}
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#InjectMocks
private Controller Controller;
#Mock
private RestTemplate rt;
#Value("${file}")
private String file;
#Spy
private DataProvider dp;
#Before
public void setup() throws Exception {
dp = new DataProvider(file);
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.rt = (RestTemplate) this.wac.getBean("restTemplateMock");
}
#Test
public void testGetResponse() throws Exception {
String[] strings = {"request", "100"};
//Set the request params from the client request
Map<String, String> parameters = new HashMap<String, String>();
parameters.put(Constants.PARAM_SINGLELINE, strings[0]);
parameters.put(Constants.PARAM_FORMAT, Constants.PARAMS_FORMAT.JSON);
Mockito.when(
rt.getForObject(Mockito.<String> any(), Mockito.<Class<Object>> any(), Mockito.<Map<String, ?>> any()))
.thenReturn(populateTestResponse());
mockMvc.perform(get(Controller_URL, strings)
.accept(Constants.APPLICATION_JSON_UTF8))
.andDo(MockMvcResultHandlers.print());
Mockito.verify(rt, Mockito.times(1)).getForObject(Mockito.<String> any(), Mockito.<Class<?>> any(), Mockito.<Map<String, ?>> any());
}
private Response populateTestResponse() {
Response resp = new Response();
resp.setScore(new BigDecimal(100));
resp.setData("Some Data");
return resp;
}
}
org.springframework.boot.test.mock.mockito.MockBean #MockBean helped me out.

Categories