SpringBoot unit test controller mvc return always 404 - spring-boot

I'm trying to run a simple unit test for my controllers but for all requests I try MockMvc returns me a 404 error.
Here is a sample of controller:
#RestController
#RequestMapping("/airports")
public class AirportController {
private final AirportRepository repository;
...
#GetMapping(value = "/no-page", produces = "application/json; charset=utf-8")
public List<Airport> noPage() {
try {
return repository.findAllByActive(true);
} catch (Exception e) {
throw new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR, "Failed to retrieve from DB!", e);
}
}
}
And a test:
#ActiveProfiles("test")
#SpringBootTest
#AutoConfigureMockMvc
public class AirportControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void testAirportController() throws Exception {
this.mockMvc.perform(get("/api/airports/no-page"))
.andDo(print())
.andExpect(status().isOk());
}
}
(The request is on /api because it's my servlet context)
The status is never 200 (what it should be), but always 404 not found.
I also try to create a test configuration like this and import it to my test:
#Configuration
#EnableWebMvc
#ComponentScan("com.project")
public class TestConfiguration implements WebMvcConfigurer {
}
But it changes nothing.
What is wrong with my test ?

Your AirportController is mapped to /airports. Therefore your test should also use that path as follows:
this.mockMvc.perform(get("/airports/no-page"))
.andDo(print())
.andExpect(status().isOk());
Please note that MvcMock test runs independent of the configured servlet context path.

Related

MockMvc not working for DataIntegrityViolationException

We Spring developers know that if one tries to delete an entity that has other associated entities, a DataIntegrityViolationException is thrown.
I wrote a delete method catching both EmptyResultDataAccessException and DataIntegrityViolationException exceptions, throwing custom service-level exceptions for each case:
#Service
public class CityService {
#Autowired
private CityRepository repository;
public void delete(Long id) {
try {
repository.deleteById(id); // returns 204
}
catch (EmptyResultDataAccessException e) {
throw new ResourceNotFoundException("Id not found " + id); // returns 404
}
catch (DataIntegrityViolationException e) {
throw new DatabaseException("Integrity violation"); // returns 400
}
}
}
I've set all up so the first scenario returns 204, the second scenario returns 404, and the third scenario returns 400. Everything is working fine when I test it on Postman.
However, when I try to write an integrated test using MockMvc, the DataIntegrityViolationException scenario doesn't work! (the other two scenarios work).
#SpringBootTest
#AutoConfigureMockMvc
#Transactional
public class CityControllerIT {
#Autowired
private MockMvc mockMvc;
(...)
#Test
public void deleteShouldReturnBadRequestEventWhenDependentId() throws Exception {
mockMvc.perform(delete("/cities/{id}", 1L))
.andExpect(status().isBadRequest());
}
}
It's returning 204 instead of 400! I have printed some messages inside the try block and I have found that it is really not throwing an exception. The try block executes entirely, as there was no integrity violation.
#Service
public class CityService {
#Autowired
private CityRepository repository;
public void delete(Long id) {
try {
System.out.println("START");
repository.deleteById(id);
System.out.println("FINISH");
}
(...)
I am missing something about MockMvc fundamentals? Why integrity violation is being ignored when executing that MockMvc test?
I've saved a minimum-H2-just-clone-and-run project on Github:
https://github.com/acenelio/mockmvc-dataintegrity
DataIntegrityViolationException does NOT work properly with #Transactional, even in a common service method like:
#Service
public class MyService {
#Autowired
private MyRepository repository;
#Transactional
public void delete(Long id) {
try {
repository.deleteById(id);
}
catch (DataIntegrityViolationException e) {
// do something
}
}
}
Similarly, if you want to automate test a DataIntegrityViolationException scenario, you should NOT annotate your test class with #Transactional.
So if your writing transactional integrated tests (which rollback database for each test), you may want to create another test class without #Transactional annotation to test your DataIntegrityViolationException scenario.

Multiple Tests with MockRestServiceServer

My Tests run through when executing them separatly. When I execute the Test Class, then one of them fails:
java.lang.AssertionError: Further request(s) expected leaving 1 unsatisfied expectation(s).
0 request(s) executed.
Test-Class:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
#ActiveProfiles("test")
#Transactional
public class ProductListControllerIT {
#Autowired RestTemplate restTemplate;
#Autowired MockMvc mvc;
#Test
public void testGet_1() throws Exception {
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(ExpectedCount.once(),
requestTo(/* any url */))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(/* JSON-STRING */)
);
var model = mvc.perform(MockMvcRequestBuilders.get("/url")
.andReturn().getModelAndView().getModel();
mockServer.verify();
}
#Test
public void testGet_2() throws Exception {
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(ExpectedCount.once(),
requestTo(/* any url */))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(/* JSON-STRING */)
);
var model = mvc.perform(MockMvcRequestBuilders.get("/url")
.andReturn().getModelAndView().getModel();
mockServer.verify();
}
}
One test pass, the other fails with error message as described above.
Thanks for hints.
I apologize. I run in a cache pitfall. The first test activated the cache for the rest call, the second rest call in the second tets has not been executed.
I clear all caches now after tests:
#After
public void after() {
mockServer.verify();
cacheManager.getCacheNames().forEach(n -> cacheManager.getCache(n).clear());
}

How to disable SpringSecurity in Junit Test class using spring boot

I have created simple Rest Api service using spring boot 2.2.5.RELEASE, I have just enabled Spring Security in the application. The JUnits are not working. I tried some of the ways to solve this issue but its not working.
Looking at references in books and online (including questions answered in Stack Overflow) I learned about two methods to disable security in tests:
#WebMvcTest(value =MyController.class, secure=false)
#AutoConfigureMockMvc(secure = false)
#EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class})
All these annotation i tried one by one on Test class but its not working.
1.#WebMvcTest(value =MyController.class, secure=false)
2.#AutoConfigureMockMvc(secure = false)
Both of these settings were identified in various Stack Overflow answers as being deprecated, but I tried them anyway.
Unfortunately, they didn't work. Apparently, in Version 2.2.1 of Spring Boot (the version I am using) secure isn't just deprecated, it is gone. Tests with the annotations using the "secure = false" parameter do not compile.
The code snippet looks like this:
Code Snippet
package com.akarsh.controller;
import static org.junit.Assert.*;
#RunWith(SpringRunner.class)
#AutoConfigureMockMvc
#EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class})
#SpringBootTest(classes = SpringBootProj2Application.class,webEnvironment =SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SurveyControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private SurveyService surveyService;
#Test
public void retrieveDetailsForQuestion_Test() throws Exception {
Question mockQuestion = new Question("Question1",
"Largest Country in the World", "Russia", Arrays.asList(
"India", "Russia", "United States", "China"));
Mockito.when(
surveyService.retrieveQuestion(Mockito.anyString(), Mockito
.anyString())).thenReturn(mockQuestion);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/surveys/Survey1/questions/Question1").accept(
MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
String expected = "{\"id\":\"Question1\",\"description\":\"Largest Country in the World\",\"correctAnswer\":\"Russia\",\"options\":[\"India\",\"Russia\",\"United States\",\"China\"]}";
String actual=result.getResponse().getContentAsString();
JSONAssert.assertEquals(expected,actual , false);
}
\\
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Authentication : User-->Roles
// Authorization : Role->Access
#Autowired
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("{noop}secret").roles("USER")
.and()
.withUser("akarsh").password("{noop}ankit").roles("ADMIN","USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and().authorizeRequests()
.antMatchers("/surveys/**").hasRole("USER")
.antMatchers("/users/**").hasRole("USER")
.antMatchers("/**").hasRole("ADMIN")
.and().csrf().disable()
.headers().frameOptions().disable();
}
}
\
Getting following exception
Description:
A component required a bean of type 'org.springframework.security.config.annotation.ObjectPostProcessor' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.config.annotation.ObjectPostProcessor' in your configuration.
2020-04-13 14:51:15.659 ERROR 5128 --- [ main] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener#36902638] to prepare test instance [com.akarsh.controller.SurveyControllerTest#3eb8057c]
\\
#RestController
public class SurveyController {
#Autowired
SurveyService surveyService;
#GetMapping("/surveys/{surveyId}/questions")
public List<Question> retrieveQuestionForSrvey(#PathVariable String surveyId)
{
if(surveyId!=null)
{
return surveyService.retrieveQuestions(surveyId);
}
return null;
}
#GetMapping("/surveys/{surveyId}/questions/{questionId}")
public Question retrieveQuestion(#PathVariable String surveyId,#PathVariable String questionId)
{
if(surveyId!=null)
{
return surveyService.retrieveQuestion(surveyId, questionId);
}
return null;
}
#PostMapping("/surveys/{surveyId}/questions")
public ResponseEntity<?> addQuestionForSrvey(#PathVariable String surveyId, #RequestBody Question question) {
Question createdTodo = surveyService.addQuestion(surveyId, question);
if (createdTodo == null) {
return ResponseEntity.noContent().build();
}
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(createdTodo.getId()).toUri();
return ResponseEntity.created(location).build();
}

Testing Spring Data Rest

I want to test a Spring boot 2 respository as rest controller app.
App is working well from browser ( http://localhost:8080/api/v1/ehdata ), but I cannot find an example how can I test it with Spring test environment. Very important, there are no RestControllers and Services, only Repositories annotated like this:
#RepositoryRestResource(path = EhDataRepository.BASE_PATH,
collectionResourceRel = EhDataRepository.BASE_PATH)
public interface EhDataRepository extends
PagingAndSortingRepository<EhData, Long> {
public static final String BASE_PATH="ehdata";
}
I tried with this test, but responses was empty, and status code was 404:
#RunWith(SpringRunner.class)
#SpringBootTest
#WebMvcTest(EhDataRepository.class)
public class RestTest extends AbstractRestTest {
#Autowired MockMvc mvc;
#Test
public void testData() throws Exception {
mvc.perform(get("/api/v1/ehdata")
.accept(MediaTypes.HAL_JSON_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_TYPE,
MediaTypes.HAL_JSON_VALUE+";charset=UTF-8")
.andReturn();
}
}
thx,
Zamek
You will need to mock the output from the respository like this based on the method you are trying to test:
#MockBean
private ProductRepo repo;
And then
Mockito.when(this.repo.findById("PR-123")
.get())
.thenReturn(this.product);
this.mvc.perform(MockMvcRequestBuilders.get("/products/{id}", "PR-123")
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andReturn();
Also, remove the server-context-path while calling API in perform() method.

How can I compare ModelAndView objects in Junit testing?

Currently the test shows that both objects returned are the same, but the assert fails. Is there any way to compare them?
#Test
public void test_search() throws Exception {
TestObject testObject= createTestObject();
ModelAndView expectedReturn = new ModelAndView("example/test", "testForm", testObject);
expectedReturn.addObject("testForm", testObject);
ModelAndView actualReturn = testController.search(testObject);
assertEquals("Model and View objects do not match", expectedReturn, actualReturn);
}
I would recommend you write a real Spring MVC Test.
E.g. like I did with spring boot
#AutoConfigureMockMvc
#SpringBootTest(classes = {YourSpringBootApplication.class})
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
#RunWith(SpringRunner.class)
public class RestControllerTest {
#Autowired
private MockMvc mvc;
#org.junit.Test
public void test_search() throws Exception {
TestObject testObject= createTestObject();
mvc.perform(MockMvcRequestBuilders
.get("/yourRestEndpointUri"))
.andExpect(model().size(1))
.andExpect(model().attribute("testObject", testObject))
.andExpect(status().isOk());
}
}
The important thing is to check your model attributes with the org.springframework.test.web.servlet.result.ModelResultMatchers.model() method (in the example statically imported)

Resources