« Controllers and validation with Waffle
November 20, 2008 • ☕️ 1 min read
One of the nicest features of Waffle are the Validators.
In order to have server side validation on some controller logic you don’t need to extend or write any fluff.
Given a method on your controller like this one:
package waffle.controllers;import org.codehaus.waffle.view.RedirectView;import org.codehaus.waffle.view.View;public class UserLoginController {public View login(String username, String password) {return new RedirectView("homepage.waffle");}}
All you have to do is write a class named UserLoginControllerValidator (just for convention, not strictly necessary) and a method with the same syntax as the action on the Controller with also the ErrorsContext parameter in the signature.
For a simple application login it will look more or less like this:
package waffle.validators;import org.codehaus.waffle.validation.ErrorsContext;import org.codehaus.waffle.validation.FieldErrorMessage;import waffle.services.AuthenticationExeption;import waffle.services.AuthenticationService;public class UserLoginControllerValidator {private final AuthenticationService authenticationService;public UserLoginControllerValidator(AuthenticationService authenticationService) {this.authenticationService = authenticationService;}public void login(ErrorsContext errors, String username, String password) {try {authenticationService.login(username, password);} catch (AuthenticationExeption exeption) {errors.addErrorMessage(new FieldErrorMessage("name", username, exeption.getMessage()));}}}
Well the design is pretty clean right?
There’s no coupling, there is no inheritance involved and the responsibilities of the classes are well isolated: the controller controls, redirects, the validator validates.
With such an easy design also testing becomes easier.
Here a sample test to check that the proper message has been added to the ErrorContext.
package waffle.controllers;import static org.mockito.Matchers.argThat;import static org.mockito.Mockito.doThrow;import static org.mockito.Mockito.mock;import static org.mockito.Mockito.verify;import org.codehaus.waffle.validation.ErrorsContext;import org.codehaus.waffle.validation.FieldErrorMessage;import org.junit.Test;import waffle.services.AuthenticationExeption;import waffle.services.AuthenticationService;import waffle.validators.UserLoginControllerValidator;public class UserLoginControllerValidatorTest {private static final String username = "test";private static final String password = "test";@Testpublic void shouldAddAnUserNotFoundErrorWhenUserNotFoundWithTheService() throws Exception {// givenAuthenticationService service = mock(AuthenticationService.class);UserLoginControllerValidator validator = new UserLoginControllerValidator(service);ErrorsContext errors = mock(ErrorsContext.class);// whendoThrow(new AuthenticationExeption("User not found")).when(service).login(username, password);validator.login(errors, username, password);// thenFieldErrorMessage expectedErrorMessage = new FieldErrorMessage("name", username, "User not found");verify(errors).addErrorMessage(argThat(new IsAnErrorMessageLike(expectedErrorMessage)));}}
I’m, indeed, using Mockito and IsAnErrorMessageLike is a custom Hamcrest Matcher, it looks like:
package waffle.controllers;import org.codehaus.waffle.validation.ErrorMessage;import org.codehaus.waffle.validation.FieldErrorMessage;import org.hamcrest.BaseMatcher;import org.hamcrest.Description;public class IsAnErrorMessageLike extends BaseMatcher {private final FieldErrorMessage fieldErrorMessage;public IsAnErrorMessageLike(FieldErrorMessage fieldErrorMessage) {this.fieldErrorMessage = fieldErrorMessage;}@Overridepublic boolean matches(Object object) {if (object == null)return false;if (!(object instanceof FieldErrorMessage))return false;FieldErrorMessage other = (FieldErrorMessage) object;if (other.getMessage().equals(fieldErrorMessage.getMessage())&& (other.getName().equals(fieldErrorMessage.getName()))&& (other.getType().equals(fieldErrorMessage.getType()))&& (other.getMessage().equals(fieldErrorMessage.getMessage()))&& (other.getValue().equals(fieldErrorMessage.getValue())))return true;return false;}@Overridepublic void describeTo(Description description) {description.appendText(fieldErrorMessage.toString());}}