Assertion error using #RequestMapping annotation outside of the class
I am getting this error message:
java.lang.AssertionError: Status
Expected :200
Actual :404
My Controller is like this
#Service
#RestController
#RequestMapping("/execute/files")
#ResponseBody
public class ControllerFiles {
#Autowired
#Qualifier("fileRunner")
ProcessRunnerInterface processRunnerInterfaceFiles;
public InputState executeRestFile(#RequestParam String name) throws ExecutionFailedException, URISyntaxException {
///code///
}
public List<String>....{
///code///
}
}
My Test
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class ControllerFilesTest {
#Autowired
private MockMvc mockMvc;
#Autowired
ControllerFiles controllerFiles;
#Test
public void testSpringMvcGetFiles() throws Exception {
this.mockMvc.perform(get("/execute/files").param("name", "Spring Community Files"))
.andDo(print()).andExpect(status().isOk());
}
}
But when I have my code like this the test work fine!
#Service
#RestController
public class ControllerFiles {
#Autowired
#Qualifier("fileRunner")
ProcessRunnerInterface processRunnerInterfaceFiles;
#RequestMapping("/execute/files")
#ResponseBody
public InputState executeRestFile(#RequestParam String name) throws ExecutionFailedException, URISyntaxException {
///code///
}
public List<String>....{
///code///
}
}
Any ideas what is going wrong?
The methods in your RestController need to be marked as #RequestMapping if you want them to be picked up as request resources. If you want to keep the base request mapping at the controller level as in your first RestController then you need to do the following:
#RestController
#RequestMapping("my/path")
public class MyController {
#RequestMapping("/")
public InputState myMethod() {
...
}
}
As it is said in documentation:
In the above example, #RequestMapping is used in a number of places. The first usage is on the type (class) level, which indicates that all handler methods in this controller are relative to the /appointments path.
So the class level #RequestMapping is only indicating relativnes. It is not declare actual resource paths based on public methods only. So you need to annotate your method like this:
#GetMapping
public InputState executeRestFile(#RequestParam String name) throws Exception {
// omited
}
Or like this:
#RequestMapping(method = RequestMethod.GET)
public InputState executeRestFile(#RequestParam String name) throws Exception {
// omited
}
Related
#RestController()
#RequestMapping(path = "/users")
public class UserController {
#GetMapping()
public #ResponseBody Page<User> getAllUsers(#RequestParam Integer pageSize, UserRequest userRequest) {
//TODO: some implementation
}}
public class UserRequest{
public String name;
public String age;
}
send the request with invalid parameter, like localhost:8800/users?name1=1234, I want to return error. but in fact it ignore the invalid parameter name1.
I tried to add the user defined annotation on the method parameter and on the class , codes like below
#RestController()
#RequestMapping(path = "/users")
#Validated
public class UserController {
#GetMapping()
public #ResponseBody Page<User> getAllUsers(#RequestParam #Validated Integer pageSize, #Validated UserRequest userRequest} {
//TODO: some implementation
}
}
But it does not working.
I think it is happened because framework has ignore the invalid parameter before the method was called.
where did framework handle the url and how can I do to make it return error instead of ignore?
You can reject parameters that are not valid. You can do so in a HandlerInterceptor class.
Reference: Rejecting GET requests with additional query params
In addition to what is done in the above reference, in your addInterceptors, you can specify the path that is intercepted.
Like this:
#Configuration
public class WebConfig implements WebMvcConfigurer {
private String USERS_PATH = "/users";
// If you want to cover all endpoints in your controller
// private String USERS_PATH = List.of("/users", "/users/**");
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new FooHandlerInterceptor()).addPathPatterns(USERS_PATH);
}
}
I use
#ResponseStatus annotation
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public void create(#RequestBody #Valid RestaurantCreationDTO restaurantCreationDTO,
BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
throwExceptionIfBindingResultHasErrors(bindingResult);
}
restaurantService.create(restaurantCreationDTO);
}
But how can I test this method with mockito? I want to test if it's returning HttpStatus.CREATED.
You will need for example to create a #WebMvcTest that allows you to test MVC Controllers. Check the example below:
#RunWith(SpringRunner.class)
#WebMvcTest(YourRestController.class)
public class YourRestControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void yourTest() throws Exception {
mvc.perform(get("/api")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated());
}
}
For more details check https://www.baeldung.com/spring-boot-testing#unit-testing-with-webmvctest.
I have declared an interface as a property of my #RestController. That interface just has a few fields/setters/getters.
The implementation of the RestController and GetMapping look like this:
#RestController
#EnableAutoConfiguration
public class AccountController {
private AccountStoreInterface store;
#GetMapping(value="/account")
public Account readAccount(#RequestParam("id") String id) throws AccountNotFoundException {
Account a = store.getAccount(id);
if (a.getId().isEmpty()) {
throw new AccountNotFoundException();
}
return a;
}
#ExceptionHandler(AccountNotFoundException.class)
#ResponseStatus(NOT_FOUND)
public #ResponseBody String handleAccountNotFoundException(AccountNotFoundException ex) {
return ex.getMessage(); }
#ExceptionHandler(NullPointerException.class)
#ResponseStatus(INTERNAL_SERVER_ERROR)
public #ResponseBody String handleNullPointerException(NullPointerException ex) {
return ex.getMessage();
}
}
The interface declaration looks like this:
public interface AccountStoreInterface {
public Account getAccount(String id) throws AccountNotFoundException;
public Account setAccount(String id, Account account) throws AccountConflictException;
}
I would like to test this using spring-boot-starter-test and junit4. I expect the following test to return a 500 because I have not passed any store object that implements my interface so it should throw a NullPointerException.
How do I configure my unit tests in order to test the 500 and 404 status codes?
Right now, the below test actually fails because the returned status is 200, which I don't understand how junit gets to.
#RunWith(SpringRunner.class)
#WebMvcTest(AccountController.class)
public class TestAccountController {
#Autowired private MockMvc mockMvc;
#Autowired private WebApplicationContext wac;
#MockBean
private AccountController accountController;
#Test
public void testGetAccountNotFound() throws Exception {
mockMvc.perform(get("/account?id={id}", "test-account-id-123")
.accept(APPLICATION_JSON)
.characterEncoding("UTF-8"))
.andDo(print())
.andExpect(status().isNotFound());
}
}
Controller needs uses .htm extensions for all handlers, including JSON REST endpoints. How should I test for REST endpoints?
Problem:
I cannot disable suffix interpretation and I am getting 406 "Could not find acceptable representation"
Tried attempts:
I reviewed posts on stackoverflow related to 406, but could not find relevant one to the case where 'htm' suffix is used in tests. When you remove '.htm' suffix from both Controller and Test - the test is passing.
Here is controller with /changePassword.htm endpoint:
#Controller
public class MainController {
public static class ResultBean {
private final String result;
public String getResult() {
return result;
}
public ResultBean(String result) {
this.result = result;
}
}
#RequestMapping(value="/changePassword.htm", method= RequestMethod.POST, produces = { "application/json" })
public #ResponseBody ResultBean changePassword (
#RequestParam("username") String username, #RequestParam("password") String password) {
return new ResultBean("OK");
}
}
And here is the test with configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { HomeControllerTest.Config.class })
public class HomeControllerTest {
#InjectMocks
private MainController controller = new MainController();
private MockMvc mvc;
#Configuration
#EnableWebMvc
public static class Config extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.favorParameter(true)
.parameterName("mediaType")
.ignoreUnknownPathExtensions(true)
.ignoreAcceptHeader(false)
.useJaf(false)
.defaultContentType(MediaType.APPLICATION_JSON);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.build();
}
#Test
public void shouldPassChangePasswordBean() throws Exception {
mvc.perform(post("/changePassword.htm")
.accept("*/*")
.param("username", "example")
.param("password", "abcdef")
)
.andExpect(status().isOk()); // Test produces 406 instead of 200
}
}
Any idea?
On newer version of Spring (4+ I think), mime type is determined from suffix first.
So If you use a .htm suffix, Spring will default to produce HTML even if you don't want to.
One way to bypass this is to use a filter that rewrite URL. For instance tuckey URL rewriter filter
With this, you can set some rules like:
/my/page/that/return/json.htm is rewriten to /my/page/that/return/json so that Spring can produce data according to the Accept header.
with Spring 5, try changing your URL of your web service to .json! that is the right fix. great details here http://stick2code.blogspot.com/2014/03/solved-orgspringframeworkwebhttpmediaty.html
How to do I ignore the class level #RequestMapping("/home") and directly call the method level #RequestMapping("/users") in Spring?
#Controller
#RequestMapping("/home")
#RequestMapping("/method1")
public void method1(){
...
}
#RequestMapping("/users")
public void listUsers(){
...
}
I want to call http://localhost:8080/users to invoke listUsers() method.
You cannot bypass requestmapping defined at class level. for If so why you want a class level mapping then... you can instead do something like this in the method level request mapping
#Controller
#RequestMapping("/home/method1")
public void method1(){
...
}
#RequestMapping("/users")
public void listUsers(){
...
}
In that case you may try this...
#Controller
#RequestMapping({ "/home", "/users" })
#RequestMapping("/method1")
public void method1(){
...
}
#RequestMapping(method="RequestMethod.GET")
public void listUsers(){
...
}
Change #RequestMapping("/users") for #RequestMapping(method=RequestMethod.GET)
From my understanding, what you expect is a class-level RequestMapping. The method-level RequestMapping should be under the path of class-level's.
I'll give you some examples:
#RestController
#RequestMapping("/home")
public class HomeController {
// Full path of following endpoint: /home/parents.
#RequestMapping(value = "/parents", method = RequestMethod.GET)
public ResponseEntity<List<People>> getParents() {
return ResponseEntity.ok(methodToGetParents());
}
For the path of "users" you should do it in another class (controller):
#RestController
#RequestMapping("/users")
public class UsersController {
// Full path of following endpoint: /users.
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<People>> getUsers() {
return ResponseEntity.ok(methodToGetUsers());
}