Skip to content

Lab 14 - Validate Order

Goal

Validate the Create Order form to tell the user if there are errors.

A. Add Validation Annotations

  1. Add the following unit test to your project. When you run it, it should fail.

    package com.welltestedlearning.cvm;
    
    import org.hibernate.validator.HibernateValidator;
    import org.junit.Before;
    import org.junit.Test;
    import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
    
    import javax.validation.ConstraintViolation;
    import java.util.Set;
    
    import static org.assertj.core.api.Assertions.assertThat;
    
    public class CreateOrderFormValidationTest {
    
      private LocalValidatorFactoryBean localValidatorFactory;
    
      @Before
      public void setup() {
        localValidatorFactory = new LocalValidatorFactoryBean();
        localValidatorFactory.setProviderClass(HibernateValidator.class);
        localValidatorFactory.afterPropertiesSet();
      }
    
      @Test
      public void invalidCoffeeOrderFormShouldReport4Errors() {
        CoffeeOrderForm coffeeOrderForm = new CoffeeOrderForm();
        coffeeOrderForm.setName("A");     // should not be only 1 letter
        coffeeOrderForm.setSize("");      // should not be blank
        coffeeOrderForm.setSweetener(""); // should not be blank
        coffeeOrderForm.setCreamer("");   // should not be blank
    
        Set<ConstraintViolation<CoffeeOrderForm>> constraintViolations = localValidatorFactory.validate(coffeeOrderForm);
    
        assertThat(constraintViolations)
            .hasSize(4);
      }
    
      @Test
      public void validFormShouldResultInNoErrors() throws Exception {
        CoffeeOrderForm coffeeOrderForm = new CoffeeOrderForm();
        coffeeOrderForm.setName("Alicia");
        coffeeOrderForm.setSize("LARGE");
        coffeeOrderForm.setSweetener("sugar");
        coffeeOrderForm.setCreamer("MILK");
    
        Set<ConstraintViolation<CoffeeOrderForm>> constraintViolations = localValidatorFactory.validate(coffeeOrderForm);
    
        assertThat(constraintViolations)
            .isEmpty();
      }
    }
    
  2. Annotate the fields (member variables) of the CreateOrderForm JavaBean so that:

    • The name must have 2 or more characters, but no more than 31 (so 31 is OK, but not 32).

      Class Name Conflict

      Because our domain has a class Size, which is also the name of the validation constraint annotation, you'll have to use the fully-qualified package (i.e., @javax.validation.constraints.Size()) when applying the annotation. Using @Size() by itself will cause a compilation error.

    • The size, sweetener, and creamer must not be blank

      Validation Annotations

      Use this summary of annotations at: https://javaee.github.io/tutorial/bean-validation002.html.

  3. Re-run the CreateOrderFormValidationTest, which should now pass.

B. Validate Form Submission

To turn on validation for the form submission, in the CoffeeOrderWebController class, find the method that's POST-mapped to /order-coffee and:

  1. Annotate the CreateOrderForm parameter with @Valid annotation.

  2. Add a BindingResult parameter to the method (no annotations needed for this)

  3. Return the original form view if there are validation errors in the BindingResult (i.e., result.hasErrors() is true)

Example from Spring's PetClinic

This is an example from the PetClinic sample application from Spring:

@PostMapping("/owners/new")
public String processCreationForm(@Valid Owner owner, BindingResult result) {
   if (result.hasErrors()) {
     return "createOwnerForm";
   }
   owners.save(owner);
   return "redirect:/owners/" + owner.getId();
}

C. Display Errors on Form

  1. Update the order-coffee.html template and add the display of error messages next to each input field. For example, to display any errors for an accountName field, if you had this before:

    <p>Name: <input type="text" th:field="*{accountName}"/></p>
    

    You would add a <span> tag with the th:if and th:errors attributes, like this:

    <p>Name: <input type="text" th:field="*{accountName}"/>
      <span th:if="${#fields.hasErrors('accountName')}" 
            th:errors="*{accountName}">Name Error</span>
    </p>
    
  2. You can also add a general error message error telling the customer that there are errors on the form like this:

    <div th:if="${#fields.hasErrors('*')}" 
         style="color: #dc3545; font-weight: 500;">
      There's a problem with your order.</div>
    

    More Validation and Error Messages

    For more information and details about other ways of displaying errors can be found here: https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#validation-and-error-messages.

    For example, instead of #fields.hasErrors('*') you can use #fields.hasAnyErrors().

    And, instead of using an inline style (as above), you can use a CSS class if there are errors, e.g.: th:errorclass="warning", which will only use the class if there are errors.

  3. Try it out by going to localhost:8080/order-coffee and seeing what errors you can cause to be displayed.



Once you've completed the above steps,
check in with the instructor to review your code.