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();
}
}
Related
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.
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.
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
I try to test a #RestController within a integration test suite using MockMvc.
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class WebControllerIT {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getStatusReurnsSomething() throws Exception {
this.mockMvc.perform(get("/status")).andExpect(status().isOk());
}
}
The #RestController (WebController) calls an injected #Service (RestClientService) which uses RestTemplate to call another REST server. This leads to the following error when running the test.
org.springframework.web.client.ResourceAccessException: I/O error on
GET request for "http://test123.com/42/status": test123.com; nested
exception is java.net.UnknownHostException: test123.com
I used MockRestServiceServer for the integration test of the #Service itself but have no idea how to archieve this within the test of #RestController.
How can I simulate a correct REST call of the RestTemplate?
The #RestController class.
#RestController
public class WebController {
private final RestClientService service;
#Autowired
public WebController(RestClientService service) {this.service = service;}
#GetMapping("/status")
public String getStatus() {
// extract pid from database ...
int pid = 42;
return this.service.getStatus(42);
}
}
The #Serviceclass.
#Service
public class RestClientService {
private final RestTemplate restTemplate;
public RestClientService(RestTemplate restTemplate) {this.restTemplate = restTemplate;}
public String getStatus(int pid) {
String url = String.format("http://test123.com/%d/status", pid);
return this.restTemplate.getForObject(url, String.class);
}
}
Integration/Unit testing doesn't work that way.Objective of this kind of testing is to run through your code and make sure all the business requirement are met but not to hit other system or DB.Here in your case u shouldn't be hitting test123.com to get back data.What needs to done here is that you should mock that method.
public String getStatus(int pid) {
String url = String.format("http://test123.com/%d/status", pid);
return this.restTemplate.getForObject(url, String.class);
}
So that control doesn't enter this method but return you back the mock data(Dummy data).
For example let say that there are 2 kind of status this method is returning and you need to do some business validation based on the string returned.In this case u need to write 2 integration test and make sure the mocking method returns 2 different value(Dummy value instead of hitting that end point)
Reason why we are writing unit testing/integration testing is to make sure your entire code is working as expected but not to hit other system from ur code.
If you want to only test your controller layer, you would do like this.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MockServletContext.class)
#WebAppConfiguration
public class WebControllerIT {
private MockMvc mockMvc;
private RestClientService service
#Mock
private RestTemplate restTemplate
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
service = new RestClientService(restTemplate);
WebController webController = new WebController(service);
mvc = MockMvcBuilders.standaloneSetup(webController).build();
}
#Test
public void getStatusReurnsSomething() throws Exception {
//Mock the behaviour of restTemplate.
doReturn("someString").when(restTemplate).getForObject(anyString(), anyString());
this.mockMvc.perform(get("/status")).andExpect(status().isOk());
}
}
I am trying to write a unit test to verify that a request returns the content of a static html file. The page is rendered when running the server, but there is no content in the test response.
Controller class:
#Controller
public class IndexController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public String index() {
return "index.html";
}
}
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class IndexControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void shouldReturnIndexPage() throws Exception {
File index = new ClassPathResource("static/index.html").getFile();
String html = new Scanner(index).useDelimiter("\\z").next();
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("index.html"))
.andExpect(content().string(html));
}
}
What am I missing?
EDIT:
I've got a working test which involves actually starting the server. My goal however was to not have to do that, by using #WebMvcTest. Not sure if that's possible or not. I consider this a workaround (unless it's the only way) and am still looking for a solution (that doesn't require starting the server).
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class IndexControllerTest {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void shouldReturnIndexPage() throws Exception {
File index = new ClassPathResource("static/index.html").getFile();
String html = new Scanner(index).useDelimiter("\\z").next();
String responseBody = restTemplate.getForObject("/", String.class);
assertThat(responseBody).isEqualTo(html);
}
}
I came across this question facing the same issue, I managed to build a test using the MockMvc like this
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class homePageControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void testIndex() throws Exception{
File login = new ClassPathResource("static/login.html").getFile();
String html = new String(Files.readAllBytes(login.toPath()));
this.mockMvc.perform(get("/login.html"))
.andExpect(status().isOk())
.andExpect(content().string(html))
.andDo(print());
}
}
When testing by doing http request, you have to spawn the whole server, so he can handle the request i.e headers, status and such
hope it helps