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
Related
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());
}
}
The status I expect when testing is 200, but I get 404 instead right now.
I am fairly new to Mockido, so if there is something simple that I am missing. Please let me know.
I have created a POST request in my controller that takes a List of Long objects. If no exception happens, returns OK for status:
#PostMapping(path = "/postlist")
public ResponseEntity<Void> updateAllInList(#RequestBody List<Long> ids) {
try {
// method from ControllerService.java here using ids
return ResponseEntity.status(HttpStatus.OK).body(null);
} catch (InvalidContentException e) {
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(null);
}
When I POST using a REST client, I am getting the correct results. The raw payloads I POST are like this:
[
2, 1
]
However, the unit test is giving me a 404.
The way I created the Test class is like this:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy({ #ContextConfiguration(classes = RootConfiguration.class), #ContextConfiguration(classes = WebConfiguration.class) })
#Category(UnitTest.class)
public class ControllerTest {
private static final String POST_REQUEST = "[ 2, 1 ]";
#Autowired private WebApplicationContext webApplicationContext;
#Autowired private ControllerService controllerService;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
doNothing().when(this.controllerService).updateAllInList(anyList());
doThrow(InvalidContentException.class).when(this.controllerService).updateAllInList(null);
}
#Test
public void updateList() throws Exception {
this.mockMvc.perform(post("http://testhost/api/configuration/postlist").contentType(MediaType.APPLICATION_JSON_UTF8).content(POST_REQUEST))
.andExpect(status().isOk());
}
#Configuration
static class RootConfiguration {
#Bean
public ControllerService ControllerService() {
return Mockito.mock(ControllerService.class);
}
}
#Configuration
#EnableWebMvc
static class WebConfiguration extends WebMvcConfigurerAdapter {
#Autowired
private ControllerService controllerService;
#Bean
public Controller controller() {
return new Controller(controllerService);
}
}
}
My theory is that in my test class I am plugging in the wrong content. But why can't we plug in the same content as the one we use from real POST raw payload?
Thanks.
When you use MockMvc, you want to trigger the mapping of your controller, not the HTTP server.
Instead of mockMvc.perform(post("http://testhost/api/configuration/postlist")...
Try mockMvc.perform(post("/configuration/postlist")...
I try to junit the following controller
#RequestMapping(value="actions.htm", params="reqType=delete",method=RequestMethod.POST)
#ResponseBody
public String deletePendingAction(#RequestParam("aPk") Long aPk)
{
pendingActionsService.deletePendingAction(aPk);
return "Deleted";
}
I use params="reqType=delete" and this is I think the reason why junit fails to map to the controller. I tested all other controllers and they work fine without params tag to the controller. My junit config is:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = ApplicationConfig.class,loader = AnnotationConfigWebContextLoader.class)
#TransactionConfiguration(defaultRollback = true)
#Transactional
public class JUnitTests {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void SetupContext()
{
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void testController() throws Exception
{
this.mockMvc.perform(post("/actions.htm","reqType=delete").param("aPk","2"));
}
}
How do I translate this params tag to the spring mvc junit? Thank you
add param `reqType=delete' to url.
this.mockMvc.perform(post("/actions.htm?reqType=delete")
.param("aPk", "2")).andReturn().getResponse().getStatus());
In fact the test should be like this, based on your controller:
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Assert;
final MvcResult result = mockMvc
.perform(post("/actions.htm")
.param("reqType", "delete")
.param("aPk", "2"))
.andExpect(
status().isOk()
.andReturn());
final String content = result.getResponse().getContentAsString();
Assert.assertEquals("Deleted", content);