This post is aimed at helping rails developers who are familiar with the patterns around Active Record model validations and simple form and are moving to the Spring world.
We can start with a simple Spring Model
package com.springapp.mvc;
import javax.persistence.*;
@Entity(name = "account")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private String email;
// getters and setters ...
}
In rails we’d add validations to our User model. The model would have errors added to it at save time and an appropriate form would display those validation errors. Adding some simple validation to a comparable Active Record model would look like this:
class User < ActiveRecord::Base
validates :first_name, presence: true
validates :last_name, presence: true
validates :email, presence: true, email: true
end
In order to add the same validations to our Spring model, we have to first add some jars to our pom.xml:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.0.3.Final</version>
</dependency>
In Spring, annotations are added the model to achieve the same result. The errors are added to the model and can be displayed similarly on the form.
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import javax.persistence.*;
@Entity(name = "account")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@NotEmpty
private String firstName;
@NotEmpty
private String lastName;
@NotEmpty @Email
private String email;
}
The validation failures can be passed through to a form and their default error strings displayed. Here’s a controller and form snippet to demonstrate:
package com.springapp.mvc;
import javax.validation.Valid;
@Controller
public class UserController {
@Autowired
private UserRepository userRepository;
@RequestMapping(value = "/add", method = RequestMethod.POST)
public String addUser(@Valid @ModelAttribute("user") User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "users";
}
userRepository.save(user);
return "redirect:/";
}
}
<div class="container">
<div class="row">
<div class="span8 offset2">
<h1>Users</h1>
<form:form method="post" action="add" commandName="user" class="form-horizontal">
<div class="control-group">
<form:label cssClass="control-label" path="firstName">First Name:</form:label>
<div class="controls">
<form:input path="firstName"/>
<form:errors path="firstName" cssClass="error" />
</div>
</div>
<div class="control-group">
<form:label cssClass="control-label" path="lastName">Last Name:</form:label>
<div class="controls">
<form:input path="lastName"/>
<form:errors path="lastName" cssClass="error" />
</div>
</div>
<div class="control-group">
<form:label cssClass="control-label" path="email">Email:</form:label>
<div class="controls">
<form:input path="email"/>
<form:errors path="email" cssClass="error" />
</div>
</div>
<div class="control-group">
<div class="controls">
<input type="submit" value="Add User" class="btn"/>
</form:form>
</div>
</div>
</div>
</div>
</div>
The errors are displayed as the form as below:
Finally this being Pivotal Labs I obviously test drove my work. I started with some model level tests that while fun to write I don’t think I’ll be repeating in the future.
package com.springapp.mvc;
public class UserTest {
private static Validator validator;
private User user;
@Before
public void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
user = new User();
user.setFirstName("Joe");
user.setLastName("Bloggs");
user.setEmail("[email protected]");
}
@Test
public void firstNameIsEmpty() {
user.setFirstName("");
Set<constraintViolation> constraintViolations =
validator.validate( user );
assertEquals( 1, constraintViolations.size() );
assertEquals(
"may not be empty",
constraintViolations.iterator().next().getMessage()
);
}
@Test
public void lastNameIsEmpty() {
user.setLastName("");
Set<constraintViolation> constraintViolations =
validator.validate( user );
assertEquals( 1, constraintViolations.size() );
assertEquals(
"may not be empty",
constraintViolations.iterator().next().getMessage()
);
}
@Test
public void emailIsEmpty() {
user.setEmail("");
Set<constraintViolation> constraintViolations =
validator.validate( user );
assertEquals( 1, constraintViolations.size() );
assertEquals(
"may not be empty",
constraintViolations.iterator().next().getMessage()
);
}
@Test
public void emailIsInvalid() {
user.setEmail("Not An Email");
Set<constraintViolation> constraintViolations =
validator.validate( user );
assertEquals( 1, constraintViolations.size() );
assertEquals(
"not a well-formed email address",
constraintViolations.iterator().next().getMessage()
);
}
}
I much preferred the integration level test that posted the form and looked at http result codes.
package com.springapp.mvc;@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml")
public class AppTest {
private MockMvc mockMvc;@SuppressWarnings("SpringJavaAutowiringInspection")
@Autowired
protected WebApplicationContext wac;@Before
public void setup() {
this.mockMvc = webAppContextSetup(this.wac).build();
}@Test
public void postForm() throws Exception {
mockMvc.perform(post("/add")
.param("firstName", "Joe")
.param("lastName", "Bloggs")
.param("email", "[email protected]")
).andExpect(status().isFound());
}
</preThe full source can be found on github.