I have a controller method like this:
#RequestMapping(value = "/{userId}/edit", method = RequestMethod.POST)
public ModelAndView updateUser(#PathVariable(USER_ID_PATH_VAR) final long userId, #Valid #ModelAttribute(MODEL_USER_WEB) final User user, BindingResult bindingResult,
final Principal principal, final Model model, #RequestParam(value = "otherLocation", required = false) Boolean isOnlyOneLocation) {
if (bindingResult.hasErrors()) {
return new ModelAndView(URL_EDIT_USER);
}
// do something ...
return new ModelAndView(URL_FINISH_USER);
}
my test looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={ManageUsersControllerTestConfig.class})
public class ManageUserControllerTest {
#Autowired
private ManageUserController userController;
#Autowired
private Model model;
private MockMvc mockMvc;
#Autowired
private BindingResult bindingResult;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".html");
mockMvc = MockMvcBuilders
.standaloneSetup(userController)
.setViewResolvers(viewResolver)
.build();
}
#Test
public void testUpdateInstitutionWithErrors() throws Exception {
when(bindingResult.hasErrors()).thenReturn(false);
mockMvc.perform(post(WebSecurityConfig.URL_USER_OVERVIEW + "/" + USER_ID + "/" + URL_PART_EDIT)
.param(USER_ID_PATH_VAR, USER_ID))
.andExpect(status().isOk())
.andDo(print());
}
}
only thing what i want is to mock the bindingresult, the bindingResult.hasErrors() method should return false. Everytime i run this test the method return true.
Any suggestions how can i fix this error?
Thanks in advance
Use this instead:
#MockBean
private BindingResult bindingResult;
You cannot and that is not how Mock MVC works.
You should submit a valid request... Mocking doesn't work.
I had something similar situation I found bindingResult in getModelAndView
Example
ModelAndView mockResult=mockMvc
.perform(MockMvcRequestBuilders.post(YOURL_URL))
.andReturn().getModelAndView();
BindingResult BindingResult= (BindingResult)mockResult.getModel().get("org.springframework.validation.BindingResult.YourForm")
Related
I have the following controller:
#RestController
#RequestMapping(value = ROOT_MAPPING)
public class GatewayController {
#Autowired
private RequestValidator requestValidator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(requestValidator);
}
#PostMapping(value = REDIRECT_MAPPING)
public ResponseEntity<ResponseDTO> redirectEndpoint(#Validated #RequestBody RequestDTO requestDTO, BindingResult result) {
if (result.hasErrors()) {
// Handle validation errors
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
// Do other stuff
return ResponseEntity.status(HttpStatus.OK).build();
}
}
And this test class:
#RunWith(SpringRunner.class)
#WebMvcTest(GatewayController.class)
public class GatewayControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private RequestValidator requestValidator;
#MockBean
private BindingResult bindingResult;
private JacksonTester<RequestDTO> requestJacksonTester;
#Before
public void setUp() throws Exception {
JacksonTester.initFields(this, new ObjectMapper());
Mockito.when(requestValidator.supports(ArgumentMatchers.any())).thenReturn(true);
}
#Test
public void whenRedirectWithValidationErrorsThenBadRequestReturned() throws Exception {
RequestDTO request = new RequestDTO();
// Set some values
Mockito.when(bindingResult.hasErrors()).thenReturn(true);
mockMvc.perform(MockMvcRequestBuilders.post(ROOT_MAPPING + REDIRECT_MAPPING)
.contentType(MediaType.APPLICATION_JSON)
.content(requestJacksonTester.write(request).getJson()))
.andExpect(MockMvcResultMatchers.status().isBadRequest());
}
}
When I run this code the test case fail with this reason: Status
Expected :400
Actual :200
So, what I want to do is to mock the BindingResult which passed as a parameter to the redirectEndpoint method in the Controller so that when calling bindingResult.hasErrors() this should return true and the test case pass.
I did many search but with no luck. Any suggestions how to do that?
Thanks in advance.
BindingResult is not a bean in the ApplicationContext. Thus, you cannot mock it via #MockBean.
A BindingResult is created for you by Spring MVC for each incoming HTTP request.
Thus, you don't want to mock the BindingResult. In fact, you probably don't want to mock the behavior of your RequestValidator either. Rather, you should ideally use the real implementation of your RequestValidator, pass in invalid request data (via MockMvc), and then verify the response accordingly.
Note that you should be able to include the real implementation of your RequestValidator via #Import(RequestValidator.class) on the test class.
Here is my controller:
#Controller
#RequestMapping("/accounts/*")
public class AccountController {
#Autowired
private AccountService accountService;
#GetMapping
public ModelAndView home() {
final ModelAndView modelAndView = new ModelAndView();
final List<Account> accountsForCurrentUser = this.accountService.getAccountsForCurrentUser();
modelAndView.addObject("accounts", accountsForCurrentUser);
modelAndView.setViewName("pages/accounts/index");
return modelAndView;
}
#GetMapping("create")
public ModelAndView create() {
final ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("account", new Account());
modelAndView.setViewName("pages/accounts/create");
return modelAndView;
}
#PostMapping("create")
public ModelAndView createSubmit(#Valid #ModelAttribute(name = "account") Account account, BindingResult bindingResult, ModelAndView modelAndView) {
if (bindingResult.hasErrors()) {
return modelAndView;
}
return new ModelAndView("redirect:/accounts");
}
}
What I'd like to do is redirecting user to /accounts/ when the form is validated but taking him back to /accounts/create/ with errors shown if errors has been reported.
But, on error, I have:
Error resolving template "accounts/create", template might not exist or might not be accessible by any of the configured Template Resolvers
You also need set model and view name in post/create method.
By the way, handling methods with ModelAndView is valid but I think it would be better to use the String approach. It's much better to read and a standart way. So your controller will look like:
#Controller
#RequestMapping("/accounts")
public class AccountController {
#Autowired
private AccountService accountService;
#GetMapping("")
public String home(Model Model) {
List<Account> accountsForCurrentUser = this.accountService.getAccountsForCurrentUser();
model.addAttribute("accounts", accountsForCurrentUser);
return "pages/accounts/index";
}
#GetMapping("/new")
public String newAccount(Model model) {
model.addAttribute("account", new Account());
return "pages/accounts/create";
}
#PostMapping("/new")
public String createAccount(#Valid #ModelAttribute Account account, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "pages/accounts/create";
}
"redirect:/accounts";
}
}
I'm trying to test My Spring controllers using Mockito, but I can't actually get how can I do that without making everything #Mock.
Moreover test method returns me NullPointerException, as it can see no user and actually no user role at all.
Is there a way to test my controllers somehow?
(Controller class)
#Controller
#SessionAttributes("user")
#RequestMapping("/login.htm")
public class LoginController{
#Autowired
private UserDao userDao;
#Autowired
private LoginValidator loginValidator;
public LoginValidator getLoginValidator() {
return loginValidator;
}
public void setLoginValidator(LoginValidator loginValidator) {
this.loginValidator = loginValidator;
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
#RequestMapping(method = RequestMethod.GET)
public String getSendEmptyForm(ModelMap model, HttpServletRequest req) {
req.getSession().invalidate();
model.addAttribute("loginForm", new LoginForm());
return "login";
}
#RequestMapping(method = RequestMethod.POST)
public String postSubmittedForm(ModelMap model, #ModelAttribute("loginForm") LoginForm loginForm,
BindingResult result, SessionStatus status) {
//validate form
loginValidator.validate(loginForm, result);
if (result.hasErrors()) {
return "login";
}
User user = userDao.findByLogin(loginForm.getLogin());
model.addAttribute("user", user);
if (user.getRole().getName().equals("Admin")) {
model.addAttribute("usersList", userDao.findAll());
return "viewAllUsersPage";
}
if (user.getRole().getName().equals("User")){
return "userPage";
}
model.addAttribute("error", "Your role is not User or Admin");
return "errorPage";
}
}
And my testing class
#RunWith(MockitoJUnitRunner.class)
public class LoginControllerTest {
#InjectMocks
private LoginController controllerUT = new LoginController();
#Mock
private ModelMap model;
#Mock
private LoginForm loginForm;
#Mock
private BindingResult result;
#Mock
private SessionStatus status;
#Mock
private LoginValidator validator;
#Mock
private UserDao userDao;
#Mock
private User useк;
#Test
public void testSendRedirect(){
final String target = "login";
String nextPage = controllerUT.postSubmittedForm(model, loginForm, result, status);
assertEquals(target, nextPage);
}
}
First off you seem to be missing stubbing for loginForm.getLogin() and userDao.findByLogin(loginForm.getLogin()) and user.getRole().getName(). Without such stubbing, these methods called on a mock will return a default value (i.e. null).
So you may want to add :
when(loginForm.getLogin()).thenReturn(login);
when(userDao.findByLogin(login)).thenReturn(user);
when(user.getRole()).thenReturn(role);
when(role.getName()).thenReturn("Admin");
You will want to vary the return values for different tests.
Depending on your implementation classes for User and Role, you could simply supply real instances.
For a test that simulates the result to have errors you'll want to add this stubbing :
when(result.hasErrors()).thenReturn(true);
since otherwise the default false is returned.
I'm having trouble setting session attributes for a test. I am using MockMvc to test calls to a controller. The session model has a member attribute on it (representing the person who has logged in). The SessionModel object is added as a session attribute. I was expecting it to be populated in the ModelMap parameter to formBacking method below, but the ModelMap is always empty.
The controller code works fine when running through the webapp, but not in the JUnit. Any idea what I could be doing wrong?
Here is my JUnit Test
#Test
public void testUnitCreatePostSuccess() throws Exception {
UnitCreateModel expected = new UnitCreateModel();
expected.reset();
expected.getUnit().setName("Bob");
SessionModel sm = new SessionModel();
sm.setMember(getDefaultMember());
this.mockMvc.perform(
post("/units/create")
.param("unit.name", "Bob")
.sessionAttr(SessionModel.KEY, sm))
.andExpect(status().isOk())
.andExpect(model().attribute("unitCreateModel", expected))
.andExpect(view().name("tiles.content.unit.create"));
}
and here is the controller in question
#Controller
#SessionAttributes({ SessionModel.KEY, UnitCreateModel.KEY })
#RequestMapping("/units")
public class UnitCreateController extends ABaseController {
private static final String CREATE = "tiles.content.unit.create";
#Autowired
private IUnitMemberService unitMemberService;
#Autowired
private IUnitService unitService;
#ModelAttribute
public void formBacking(ModelMap model) {
SessionModel instanceSessionModel = new SessionModel();
instanceSessionModel.retrieveOrCreate(model);
UnitCreateModel instanceModel = new UnitCreateModel();
instanceModel.retrieveOrCreate(model);
}
#RequestMapping(value = "/create", method = RequestMethod.GET)
public String onCreate(
#ModelAttribute(UnitCreateModel.KEY) UnitCreateModel model,
#ModelAttribute(SessionModel.KEY) SessionModel sessionModel) {
model.reset();
return CREATE;
}
#RequestMapping(value = "/create", method = RequestMethod.POST)
public String onCreatePost(
#ModelAttribute(SessionModel.KEY) SessionModel sessionModel,
#Valid #ModelAttribute(UnitCreateModel.KEY) UnitCreateModel model,
BindingResult result) throws ServiceRecoverableException {
if (result.hasErrors()){
return CREATE;
}
long memberId = sessionModel.getMember().getId();
long unitId = unitService.create(model.getUnit());
unitMemberService.addMemberToUnit(memberId, unitId, true);
return CREATE;
}
}
For your test class add the #WebAppConfiguration annotation and autowire the following as well.(WebApplicationContext and MockHttpSession )
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration({ "classpath:springDispatcher-servlet.xml" })
public class MySessionControllerTest {
#Autowired WebApplicationContext wac;
#Autowired MockHttpSession session;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void testUnitCreatePostSuccess() throws Exception {
UnitCreateModel expected = new UnitCreateModel();
expected.reset();
expected.getUnit().setName("Bob");
SessionModel sm = new SessionModel();
sm.setMember(getDefaultMember());
session.setAttribute(SessionModel.KEY, sm);
this.mockMvc.perform(
post("/units/create")
.session(session)
.param("unit.name", "Bob")
.andExpect(status().isOk())
.andExpect(model().attribute("unitCreateModel", expected))
.andExpect(view().name("tiles.content.unit.create"));
}
}
I am trying to test my controller. Spring populates my Profile object but it is empty. I can set the email before the call bu it still is null. How to jag pass a Profile in a proper way?
private MockHttpServletRequest request;
private MockHttpServletResponse response;
#Autowired
private RequestMappingHandlerAdapter handlerAdapter;
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#Before
public void setUp() throws Exception {
this.request = new MockHttpServletRequest();
request.setContentType("application/json");
this.response = new MockHttpServletResponse();
}
#Test
public void testPost() {
request.setMethod("POST");
request.setRequestURI("/user/"); // replace test with any value
final ModelAndView mav;
Object handler;
try {
Profile p = ProfileUtil.getProfile();
p.setEmail("test#mail.com");
request.setAttribute("profile", p);
System.out.println("before calling the email is " + p.getEmail());
handler = handlerMapping.getHandler(request).getHandler();
mav = handlerAdapter.handle(request, response, handler);
Assert.assertEquals(200, response.getStatus());
// Assert other conditions.
} catch (Exception e) {
}
}
This is the controller
#RequestMapping(value = "/", method = RequestMethod.POST)
public View postUser(ModelMap data, #Valid Profile profile, BindingResult bindingResult) {
System.out.println("The email is " + profile.getEmail());
}
Try using following signature for the controller function postUser.
public View postUser(ModelMap data, #ModelAttribute("profile") #Valid Profile profile, BindingResult bindingResult)
Hope this helps you. Cheers.