Spring Boot MVC Test 404 with Valid Request - spring-mvc

I am using Spring Boot 2.0.6 and have set up a test for a controller: the method is as follows:
#Secured("ROLE_ADMIN")
#GetMapping(value = {"/maintainers/aircrafts/workorders/workitems/{wid}/parts"}, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
Response<Page<WorkItem>> getPagedParts(
#PathVariable("wid") Optional<Long> workItemId,
#PageableDefault(page = DEFAULT_PAGE_NUMBER, size = DEFAULT_PAGE_SIZE)
#SortDefault.SortDefaults({
#SortDefault(sort = "partName", direction = Sort.Direction.ASC),
#SortDefault(sort = "partSpecification", direction = Sort.Direction.ASC)
}) Pageable pageable) {
LOG.info("looking for work: {}", workItemId);
return Response.of(workItemService.findAllPartsForWorkItem(workItemId.get(), pageable));
}
As you can see, it is supposed to do paging and sorting, but it doesn't even get past the path:
The test that tests it is as follows:
#ActiveProfiles("embedded")
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#EnableConfigurationProperties
#EnableJpaRepositories({ "au.com.avmaint.api" })
#AutoConfigureMockMvc
public class WorkItemControllerPartsFunctionalTest {
private static final Logger LOG = LoggerFactory.getLogger(WorkItemControllerFunctionalTest.class);
private String adminJwtToken;
#Autowired
private WebApplicationContext context;
#Autowired
private MockMvc mvc;
#Autowired
private UserService userService;
#Autowired
private RoleService roleService;
#Autowired
private CasaService casaService;
#Autowired
private MaintainerService maintainerService;
#Autowired
private MaintenanceContractService maintenanceContractService;
#Autowired
private WorkSetService workSetService;
#Autowired
private WorkSetTemplateService workSetTemplateService;
#Autowired
private AircraftService aircraftService;
Maintainer franks;
MaintenanceContract contract;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
franks = MaintainerFixtures.createFranksMaintainer(maintainerService, maintenanceContractService, casaService);
adminJwtToken = UserAndRoleFixtures.adminToken(userService, roleService, franks);
contract = WorkItemFixtures.makeDetailedJobOnContract(franks, maintainerService, maintenanceContractService, workSetTemplateService, casaService, aircraftService);
}
#Test
public void findingWorkItemsWithoutParts() throws Exception {
Set<WorkSet> sets = contract.getWorkOrders().stream().findFirst().get().getWorkSets();
WorkSet hundredHourly = sets.stream().filter(s -> s.getName().equals("100 Hourly for PA-31")).findFirst().orElse(null);
WorkItem opening = hundredHourly.getWorkItems().stream().filter(wi -> wi.getTitle().equals("Opening the aircraft")).findFirst().orElse(null);
LOG.info("opening item: {}", opening);
LOG.info("HUNDRED: {}", hundredHourly);
mvc.perform(get("/maintainers/aircrafts/workorders/workitems/" + opening.getId() + "/parts")
.header(AUTHORIZATION_HEADER, "Bearer " + adminJwtToken))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.payload").isNotEmpty())
.andExpect(jsonPath("$.payload.content").isNotEmpty())
.andExpect(jsonPath("$.payload.pageable").isNotEmpty())
.andExpect(jsonPath("$.payload.last").value(false))
.andExpect(jsonPath("$.payload.totalPages").value(3)) // page count
.andExpect(jsonPath("$.payload.totalElements").value(9)) // total count
.andExpect(jsonPath("$.payload.size").value(4)) // elements per page
.andExpect(jsonPath("$.payload.numberOfElements").value(4)) // elements in page
.andExpect(jsonPath("$.payload.number").value(0)) // current page number
.andExpect(jsonPath("$.payload.content").isArray())
// oops, lets not check dates, they're created on the instant
.andExpect(jsonPath("$.payload.content[0].pos").value("1"))
.andExpect(jsonPath("$.payload.content[0].title").value("Opening the aircraft"))
.andExpect(jsonPath("$.payload.content[0].category").value("AIRFRAME"))
;
}
#After
public void tearDown() {
MaintainerFixtures.removeFranks(franks, maintainerService, aircraftService);
WorkItemFixtures.killJobs(workSetService, workSetTemplateService);
UserAndRoleFixtures.killAllUsers(userService, roleService);
}
}
As the project makes extensive use of JPA, there are annotations and a lot of data setup, but all of this has worked fine with other tests and there don't appear to be any problems with the data. In fact a peek at the JSON output for the work order that this method should be querying...
work order JSON
Basically has all the data correctly set up. The spring boot startup includes this line:
2018-11-12 06:32:17.362 INFO 83372 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/maintainers/aircrafts/workorders/workitems/{wid}/parts],methods=[GET],produces=[application/json]}" onto au.com.avmaint.api.common.Response<org.springframework.data.domain.Page<au.com.avmaint.api.aircraft.model.WorkItem>> au.com.avmaint.api.aircraft.WorkItemController.getPagedParts(java.util.Optional<java.lang.Long>,org.springframework.data.domain.Pageable)
So the path appears to be OK
and now to the .andDo(print()) output:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /maintainers/aircrafts/workorders/workitems/5/parts
Parameters = {}
Headers = {Authorization=[Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmcmFua0BmcmFua3MuY29tIiwic2NvcGVzIjpbIlJPTEVfQURNSU4iLCJST0xFX0JBU0lDIl0sImV4cCI6MTU0MjgyODczOH0.QOTiyWG_pVL9qb8MDG-2c_nkTnsIzceUH-5vvtmpZhBcdro9HqVADojK0-c6B1sAOOYOcprpwg4-wrBF0PGweg]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
and the 404. So I guess I'm breaking something somewhere, I just can't see what it is, can anyone help with this?

Sorry everyone, the effect of tearing my hair out for ages, finally posting the question and then finding the problem moments later.
The issue was that I forgot to put /api as the prefix on the path in the test. This prefix is put on the top of every controller with:
#RestController
#RequestMapping("/api")
public class WorkItemController {
so, yeah: it works now

Related

Integration Test: MockMvc is returning empty body for POST request

I do not know why the MvcResult returns an empty JSON for a GET request when the httpStatus is Ok. Moreover, through Postman I can tell that the data is indeed there and should be retrievable. I want to use the JSON data and map it to objects using the ObjectMapper's readValue() method. But in order to do that, I obviously need to be able to retrieve it first... help
The test does not fail, but when I try to System.out.println(json) - there is an empty line in the console where presumably the printed JSON should be. (I do not have the same issue when doing unit tests.)
`#SpringBootTest
#AutoConfigureMockMvc (addFilters = false) //in order to disable security: error 401
public class IntegrationTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Test
public void test() {
String url = "/url";
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(url))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
String json = result.getResponse().getContentAsString();
System.out.println(json);
}`

Issue testing Accept request header in Spring MockMvc

I am trying to unit test SpringBoot Controller. Objective is to test that we return 406 Not Acceptable when request header contains Accept as anything other than application/json. But I find its not working with MockMvc. I am getting 200 OK instead of 406. Any ideas please! Of course, service returns 406 as expected when I test using Postman or any rest client.
#Test
public void shouldRejectIfInvalidAcceptHeaderIsPassed() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Accept","application/xml");
httpHeaders.add("Authorization", "some jwt token");
this.mockMvc.perform(post("/sample/test")
.contentType(MediaType.APPLICATION_JSON)
.headers(httpHeaders)
.content(toJson("")))
.andExpect(status().isNotAcceptable());
// Then
verify(mockSampleService).getSampleOutput();
}
And my controller looks like,
#RestController
#Validated
#RequestMapping(PATH_PREFIX)
public class SampleController {
public static final String PATH_PREFIX = “/sample”;
private final SampleService sampleService;
#Autowired
public SampleController(SampleService sampleService) {
sampleService = sampleService;
}
#RequestMapping(value = “/test”, method = RequestMethod.POST))
public SampleResponse createSession() {
return sampleService.getSampleOutput();
}
}
You might consider using the accept() method instead, e.g.
mvc.perform( MockMvcRequestBuilders
.get("/some/test/url")
.content(...)
.accept(MediaType.APPLICATION_XML)
.andExpect(...)
Ahh! I have identified and fixed the issue. In the controller we need to have, #RequestMapping(headers="Accept=application/json"). Else MockMvc is not able to identify the expected format.

SpringWebMvcTest - Test Requestbody using #Valid and custom validation

I am trying to test my controller endpoint and my requestbody annotated with #Valid annotation. My Testclass looks like the follow:
#RunWith(SpringRunner.class)
#WebMvcTest(value = BalanceInquiryController.class, secure = false)
public class BalanceInquiryControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BalanceInquiryController balanceInquiryController;
#Test
public void testGetBalanceInquiry() throws Exception {
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post("/com/balanceInquiry")
.accept(MediaType.APPLICATION_JSON)
.content("{\"comGiftCard\":{\"cardNumber\":\"1234567890\",\"pinNumber\":\"0123\"},\"comMerchant\":\"MERCHANT1\"}")
.contentType(MediaType.APPLICATION_JSON);
MvcResult mvcResult = mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
assertEquals(HttpStatus.OK.value(), response.getStatus());
}
}
My Controller - #PostMapping looks like that:
#PostMapping(value = "/com/balanceInquiry")
public ResponseEntity<?> getBalanceInquiry(#Valid #RequestBody BalanceInquiryModel balanceInquiry, Errors errors) {
if (errors.hasErrors()) {
return new ResponseEntity<String>("Validation error", HttpStatus.BAD_REQUEST);
}
//do any stuff...
return new ResponseEntity<BalanceInquiryResponse>(balanceInquiryResponse, HttpStatus.OK);
}
My BalanceInquiryModel is annotated with #Valid and has some hibernate and custom validations behind. Those validations are all ok and already unit tested.
What I like to test is my endpoint where I send a valid json request body expecting a 200 response and also an invalid json request body expecting a 400 response validated by the set #Valid implementation.
For example an unvalid call is to send no pinNumber or length < 4.
I have read some threads and some uses MockMvcBuilders.standaloneSetup() to mock the full controller. But I wont do a full integration test.
Not quite sure how to go on with this situation and if I should go on.
P.S.: At the moment I get always a 200 response no matter if the validation should give an error or not.
Here a gist for more code and the validation classes/models.
Here's one of my example I work on my project
hope it help you out:
I have a global exception handler to handler my MethodArgumentNotValidException and throw it
#RequestMapping(value = "/add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> createUser(#Valid #RequestBody User user) {
User savedUser = userService.save(user);
return new ResponseEntity<User>(savedUser, HttpStatus.CREATED);
}
public void testAdduser() throws Exception{
final User request = new User();
request.setFirstName("Test");
request.setLastName("some description");
mockMvc.perform(post(END_POINT+"/add")
.contentType(MediaType.APPLICATION_JSON)
.content(stringify(request))
).andDo(print()).andExpect(status().isUnprocessableEntity())
;
}
private String stringify(Object object) throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(object);
}
Update:
I think your main problem is that you are using #WebMvcTest in stead of #SpringBootTest.
the different between 2 of them is that:
#SpringBootTest annotation will loads complete application and injects all the beans which is can be slow.
#WebMvcTest - for testing the controller layer. it doesn't inject other bean beside the #RestController
so if you are just testing just pure controller to see u can reach the endpont then you can just use #WebMvcTest which will make your test run faster.
but in your case, you want it to run the spring validation, you will need to use #SpringBootTest
for detailed: https://spring.io/guides/gs/testing-web/

Mockito Test Controller

This is my first attempt with Mockito.
My controller
#RequestMapping(value = "/add", method = RequestMethod.POST)
#ResponseBody
public ValidationResponse startVisitForPatient(PatientBO patientBO,Locale locale) {
ValidationResponse res = new ValidationResponse();
if (patientManagementService.startVisit(patientBO.getId())){
res.setStatus(MessageStatus.SUCCESS);
res.setValue(messageSource.getMessage("success.message", null, locale));
}
else{
res.setValue(messageSource.getMessage("failed.message", null, locale));
res.setStatus(MessageStatus.FAILED);
}
return res;
}
The service
#Transactional
public boolean startVisit(long id) {
Patient patient = patientRepository.findOne(id);
Set<Encounter> encounters = patient.getEncounters();
Encounter lastEncounter = null;
Timestamp startVisitDate = null;
Timestamp endVisitDate = null;
if (encounters.iterator().hasNext()){
lastEncounter = encounters.iterator().next();
startVisitDate = lastEncounter.getStartVisitDate();
endVisitDate = lastEncounter.getEndVisitDate();
}
if (lastEncounter == null || (endVisitDate != null && endVisitDate.after(startVisitDate))){
Encounter newEncounter = new Encounter();
newEncounter.setCreatedBy(userService.getLoggedUserName());
newEncounter.setCreatedDate(new Timestamp(new Date().getTime()));
newEncounter.setModifiedBy(userService.getLoggedUserName());
newEncounter.setModifiedDate(newEncounter.getCreatedDate());
newEncounter.setPatient(patient);
newEncounter.setStartVisitDate(newEncounter.getCreatedDate());
encounters.add(newEncounter);
return true;
}
else
return false;
}
Unit test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "file:src/main/webapp/WEB-INF/root-context.xml",
"file:src/main/webapp/WEB-INF/applicationContext.xml",
"file:src/main/webapp/WEB-INF/securityContext.xml" })
#WebAppConfiguration
public class Testing {
#InjectMocks
StaffVisitManagementController staffVisitManagementController;
#Mock
PatientManagementService patientManagementService;
#Mock
View mockView;
MockMvc mockMvc;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(staffVisitManagementController)
.setSingleView(mockView)
.build();
}
#Test
public void testStartVisit() throws Exception {
mockMvc.perform(post("/staff/visit/add").param("id", "1"))
.andExpect(status().isOk()).andExpect(content().string("success"));
}
}
The test method indeed calls the controller. However I am not able to debug the service at this line
patientManagementService.startVisit(patientBO.getId()))
All it returns is just false.
What am I missing here ?
When you mock something with Mockito, it mocks out everything to return some sort of default. For objects, this is null. For integers/doubles/etc, this is 0, for booleans, false. See the Mockito Docs. So you can't step into it because it's not your class that's present in the controller under test, it's a generated proxy that is merely pretending to be your class (hence, mocking).
If you want to change the behaviour of your class, you will need to use Mockito to tell it to return different variables depending on what is passed to the method, or which test it's running in. e.g.
when(patientManagementService.startVisit(1)).thenReturn(true);
Would mean that, if any code using the mocked PatientManagementService calls patientManagementService.startVisit(patientBO.getId()) where patientBO.getId() returns 1, then it will return true, otherwise it will return false, which is the default answer.
In your case, I suspect you would be better off mocking out patientRepository, rather than patientManagementService if you want to be able to step into your service-layer code.
EDIT:
Roughly what I would suggest is:
private StaffVisitManagementController staffVisitManagementController;
private PatientManagementService patientManagementService;
#Mock
private PatientRepository patientRepository;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(patientRepository.findOne(1)).thenReturn(new Patient());
patientManagementService = new PatientManagementService(patientRepository);
staffVisitManagementController = new StaffVisitManagementController(patientManagementService);
mockMvc = MockMvcBuilders.standaloneSetup(staffVisitManagementController)
.setSingleView(mockView)
.build();
}
Obviously, the name of repository class may be different, and you may be using field inject instead of constructor injection, etc, etc, but otherwise this should allow you to step into the PatientManagementService with the debugger. You will not be able to step into the PatientRepository, as that will be mocked.

Adobe CQ5 custom servlet path

I am trying to add some functionality to json processing for some nodes. So I wrote custom servlet extended from SlingSafeMethodsServlet which I need to be executed when user makes GET for the following url : /data/events/any_sequence/any_sequence.json or /data/events/any_sequence/any_sequence.infinity.json or for example /data/events/any_sequence/any_sequence.2.json where any_sequence of course means any valid sequence of symbols.
The problem is that I cannot find in the sling docs how to map this template like urls.
I've been trying to set properties like this:
#Component
#Service
#Properties({
#Property(name = "sling.servlet.resourceTypes", value = "data/events/-/-"),
#Property(name = "sling.servlet.extensions", value = "json"),
#Property(name = "sling.servlet.methods", value = "GET"),
#Property(name = "service.description", value = "JSON advanced renderer")
})
But it didn't help. I checked felix console and found out that my service had started and running, so the problem is how to set url mappings. So my question is how to set url mapping in my case to invoke doGet of my custom servlet ?
Thanks.
As far as I understand CQ5 does not provide ability to map custom servlets on wildcard urls. The only way to accomplish goal similiar to one I needed is to use some unique for this servlet selector like this:
#Component
#Service
#Properties({
#Property(name = "sling.servlet.resourceTypes", value = "sling/servlet/default"),
#Property(name = "sling.servlet.extensions", value = "json"),
#Property(name = "sling.servlet.selectors", value = "advanced"),
#Property(name = "sling.servlet.methods", value = "GET"),
#Property(name = "service.description", value = "JSON advanced renderer")
})
This code means that if I'll try to make GET on some node with *.advanced.json selector and extension then request will be forwarded to my custom servlet.
See http://apache-sling.73963.n3.nabble.com/Register-servlet-for-subtree-td84106.html
I've solved this, exactly as the original poster was hoping. All other answers are effectively "it can't be done" or "here's a way to do it if you're willing to dirty up your clean RESTful API with selectors"
If you want to keep the clean API you envisioned, here's how. This works also for Apis without extensions, like /myservice/mythings/123123 , where 123123 is some dynamic ID
Create Two Files:
ResourceProvider
Servlet
The ResourceProvider
The purpose of this is only to listen to all requests at /data/events and then produce a "Resource" at that virtual path, which doesn't actually exist in the JCR.
#Component
#Service(value=ResourceProvider.class)
#Properties({
#Property(name = ResourceProvider.ROOTS, value = "data/events"),
#Property(name = ResourceProvider.OWNS_ROOTS, value = "true")
})
public class ImageResourceProvider implements ResourceProvider {
#Override
public Resource getResource(ResourceResolver resourceResolver, String path) {
AbstractResource abstractResource;
abstractResource = new AbstractResource() {
#Override
public String getResourceType() {
return TypeServlet.RESOURCE_TYPE;
}
#Override
public String getResourceSuperType() {
return null;
}
#Override
public String getPath() {
return path;
}
#Override
public ResourceResolver getResourceResolver() {
return resourceResolver;
}
#Override
public ResourceMetadata getResourceMetadata() {
return new ResourceMetadata();
}
};
return abstractResource;
}
#Override
public Resource getResource(ResourceResolver resourceResolver, HttpServletRequest httpServletRequest, String path) {
return getResource(resourceResolver , path);
}
#Override
public Iterator<Resource> listChildren(Resource resource) {
return null;
}
}
The Servlet
Now you just write a servlet which handles any of the resources coming from that path - but this is accomplished by handling any resources with the resource type which is produced by the ResourceProvider listening at that path.
#SlingServlet(
resourceTypes = TypeServlet.RESOURCE_TYPE,
methods = {"GET" , "POST"})
public class TypeServlet extends SlingAllMethodsServlet {
static final String RESOURCE_TYPE = "mycompany/components/service/myservice";
#Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
final String [] pathParts = request.getResource().getPath().split("/");
final String id = pathParts[pathParts.length-1];
response.setContentType("text/html");
PrintWriter out = response.getWriter();
try {
out.print("<html><body>Hello, received this id: " + id + "</body></html>");
} finally {
out.close();
}
}
}
This is the syntax I have used to accomplish similar tasks:
#Component(immediate = true, description = "JSON advanced renderer")
#Service(value = javax.servlet.Servlet.class)
#Properties(value = {
#Property(name = "sling.servlet.extensions", value = { "json" }),
#Property(name = "sling.servlet.methods", value = { "GET" }),
#Property(name = "sling.servlet.paths", value = {
"/data/events/any_sequence/any_sequence",
"/data/events/any_sequence/any_sequence.infinity",
"/data/events/any_sequence/any_sequence.2"
})
})
I had a similar problem and I needed wildcards I used
#Service
#Component
#Properties({
#Property(name = "sling.servlet.paths", value = "/bin/resolver/gb"),
#Property(name = "sling.servlet.extensions", value = "*")
})
public class Test extends SlingAllMethodsServlet {
#Override
public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.print("The path you used is:" + request.getPathInfo());
}
}
Call is [server]:[port]/bin/resolver/gb.[wildcard]
So what can be done is : [server]:[port]/bin/resolver/gb.en/something
Everything after "." is considered an extension so need to be handled in the servlet, but helped me achieve my requirement
It seems that the path of your servlet is same.Just selectors are varying. When used with path, other things are ignored in SlingServlet. So using something like this should serve the purpose:
#SlingServlet(paths = " /data/events/any_sequence/any_sequence", extensions = "json")
You would need to add /data in execution paths from Felix console(/system/console/configMgr) as is it not there by default in Apache Sling Servlet Resolver property
This can be accomplished using the desired external URI pattern by constructing a Sling mapping or Apache rewrite to effectively move the JSON extension to just after "data" in the URI, so the single servlet at "/data" ends up receiving the arbitrary path via the suffix of the request. If you are also using data from selectors, you'll need to move them along with the extension.

Resources