Announcement Announcement Module
Collapse
No announcement yet.
Spring MVC 3.2 Unit Test: No BindingResult for attribute "attr" Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring MVC 3.2 Unit Test: No BindingResult for attribute "attr"

    Hi Folks,

    I am trying to unit test a controller

    Here is the test: -

    Code:
      @Test
      public void testRegisterMemberExists() throws Exception {
        
        doThrow(new MemberExistsServiceException()).when(memberService).registerMember(any(String.class), any(String.class));
        
        this.mockMvc.perform(
            post("/register")
            .param("email", "[email protected]")
            .param("password", "password"))
            .andExpect(status().isOk())   
            .andExpect(model().attributeHasErrors("email"));
        
      }

    Here is the method in question: -

    Code:
    @RequestMapping(value = "/register", method = RequestMethod.POST)
        public String register(@Valid @ModelAttribute(SecurityRegisterModel.KEY) SecurityRegisterModel model, BindingResult result, HttpServletRequest aRequest) {
          
            if (result.hasErrors()){
              return LOGIN;
            }
          
            try{
              memberService.registerMember(model.getEmail(), model.getPassword());
            }catch (MemberExistsServiceException mese){
              result.rejectValue("email", "member.email.exists");
              return LOGIN;
            }
          
            return "redirect:/dashboard";
        }

    Here is the model: -

    Code:
    public class SecurityRegisterModel extends ABaseModel implements Serializable {
    
        private static final long serialVersionUID = -3021884732426352101L;
    
        public final static String KEY = "securityRegisterModel";
    
        @Email
        private String email;
        @Size(min=7)
        private String password;
    
        @Override
        public void reset() {
          setEmail("");
          setPassword("");
        }
    
        @Override
        public String getKey() {
            return KEY;
        }
    
        public String getEmail() {
          return email;
        }
    
        public void setEmail(String email) {
          this.email = email;
        }
    
        public String getPassword() {
          return password;
        }
    
        public void setPassword(String password) {
          this.password = password;
        }
    
        
    
    }
    Through debugging I can see that the result.rejectValue line is called. I expected that to make the assertion "model().attributeHasErrors("email")" pass, but it doesn't.

    I also checked model().attributeExists("email"), but that fails too. I'm not sure what the problem is.

    Any ideas?
    Thanks!
    Ash

  • #2
    You can print all results from the test including all binding errors like so:

    Code:
        this.mockMvc.perform(\post("/register")
            .param("email", "[email protected]")
            .param("password", "password"))
            .andDo(print())
            .andExpect(status().isOk())   
            .andExpect(model().attributeHasErrors("email"))

    Comment


    • #3
      Originally posted by Rossen Stoyanchev View Post
      You can print all results from the test including all binding errors like so:

      Code:
          this.mockMvc.perform(\post("/register")
              .param("email", "[email protected]")
              .param("password", "password"))
              .andDo(print())
              .andExpect(status().isOk())   
              .andExpect(model().attributeHasErrors("email"))
      Thanks Rossen, that's what I was missing. I'll try it out when I get back to work next week.

      Happy New year
      Ash

      Comment


      • #4
        Hi Rossen, I've got a similar issue.

        So, my test:
        Code:
            public void saveChosenPaymentTypeTest2() throws Exception {
                //no payment type selected
                mockMvc.perform(post("/order/savePayment"))
                		.andDo(print())
                        .andExpect(model().attributeHasErrors("paymentType"))
                        .andExpect(model().errorCount(1))
                        .andExpect(status().isOk())
                        .andExpect(forwardedUrl("order.select.paymentType"));
            }
        Controller method:

        Code:
        @RequestMapping(value = "/savePayment", method = RequestMethod.POST)
        	public ModelAndView saveChosenPaymentType(@Valid SavePaymentTypeForm form, BindingResult bindingResult ,
        			HttpSession session) {
        
                if (bindingResult.hasErrors()) {
                	ModelAndView mav = new ModelAndView("order.select.paymentType");
                	mav.addObject(bindingResult.getTarget());
                	return mav;
                }
        
                ModelAndView mav = new ModelAndView("redirect:/order/confirmation");
                session.setAttribute(ORDER_PAYMENT_TYPE_SESSION_KEY, form.getPaymentType());
        		return mav;
        	}
        The class that should be validated

        Code:
        public class SavePaymentTypeForm {
        	@NotNull(message = "{order.savePayment.error.paymentType.not.selected}")
            private PaymentType paymentType;
        
            public PaymentType getPaymentType() {
                return paymentType;
            }
        
            public void setPaymentType(PaymentType paymentType) {
                this.paymentType = paymentType;
            }
        }
        and results of .andDo(print())
        Code:
        MockHttpServletRequest:
                 HTTP Method = POST
                 Request URI = /order/savePayment
                  Parameters = {}
                     Headers = {}
        
                     Handler:
                        Type = com.euroit.militaryshop.web.controller.OrderWizardController
                      Method = public org.springframework.web.servlet.ModelAndView com.euroit.militaryshop.web.controller.OrderWizardController.saveChosenPaymentType(com.euroit.militaryshop.web.form.SavePaymentTypeForm,org.springframework.validation.BindingResult,javax.servlet.http.HttpSession)
        
          Resolved Exception:
                        Type = null
        
                ModelAndView:
                   View name = order.select.paymentType
                        View = null
                   Attribute = trolley
                       value = Mock for TrolleyImpl, hashCode: 1397618175
                      errors = []
                   Attribute = savePaymentTypeForm
                       value = com.euroit.militaryshop.web.form.SavePaymentTypeForm@75044c61
                      errors = [Field error in object 'savePaymentTypeForm' on field 'paymentType': rejected value [null]; codes [NotNull.savePaymentTypeForm.paymentType,NotNull.paymentType,NotNull.com.euroit.militaryshop.enums.order.PaymentType,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [savePaymentTypeForm.paymentType,paymentType]; arguments []; default message [paymentType]]; default message [{order.savePayment.error.paymentType.not.selected}]]
        
                    FlashMap:
        
        MockHttpServletResponse:
                      Status = 200
               Error message = null
                     Headers = {}
                Content type = null
                        Body = 
               Forwarded URL = order.select.paymentType
              Redirected URL = null
                     Cookies = []
        And the test fails with message "No BindingResult for attribute: paymentType
        ". Where can be the problem?

        Comment


        • #5
          The problem is that you are not sending the data in your test. Even though you want to simulate the submission of an empty form, you should send the empty form data in the body of your request. I made the required modifications to your test:

          Code:
            
          public void saveChosenPaymentTypeTest2() throws Exception {
                  //no payment type selected
                  mockMvc.perform(post("/order/savePayment")
                          .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                          .content(TestUtil.convertObjectToFormUrlEncodedBytes(new SavePaymentForm()))
                  )
                  		.andDo(print())
                          .andExpect(model().attributeHasErrors("paymentType"))
                          .andExpect(model().errorCount(1))
                          .andExpect(status().isOk())
                          .andExpect(forwardedUrl("order.select.paymentType"));
              }
          Now, this code uses a static convertObjectToFormUrlEncodedBytes() method of the TestUtil class. This is a method which you have to implement as well. Here is an example implementation of that method (If you don't want to add Jackson in your classpath, you have to write your own implementation or just hard code the form url encoded string):

          Code:
          import org.codehaus.jackson.map.ObjectMapper;
          import org.codehaus.jackson.map.annotate.JsonSerialize;
          
          import java.util.Iterator;
          import java.util.Map;
          import java.util.Set;
          
          public class TestUtil {
              public static byte[] convertObjectToFormUrlEncodedBytes(Object object) {
                  ObjectMapper mapper = new ObjectMapper();
                  mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
          
                  Map<String, Object> propertyValues = mapper.convertValue(object, Map.class);
          
                  Set<String> propertyNames = propertyValues.keySet();
                  Iterator<String> nameIter = propertyNames.iterator();
          
                  StringBuilder formUrlEncoded = new StringBuilder();
          
                  for (int index=0; index < propertyNames.size(); index++) {
                      String currentKey = nameIter.next();
                      Object currentValue = propertyValues.get(currentKey);
          
                      formUrlEncoded.append(currentKey);
                      formUrlEncoded.append("=");
                      formUrlEncoded.append(currentValue);
          
                      if (nameIter.hasNext()) {
                          formUrlEncoded.append("&");
                      }
                  }
          
                  return formUrlEncoded.toString().getBytes();
              }
          }
          This should solve your problem.
          Last edited by Loke; Jan 25th, 2013, 04:06 AM.

          Comment


          • #6
            Unfortunately it doesn't help with the same error message.

            Comment

            Working...
            X