Roo In Action Corner – Testing Entity Validations With A Mock Entity

by
Tags: ,
Category:

This article references the Manning book Spring Roo in Action, which Ken Rimple is currently authoring.  It is available as a Manning MEAP.

I’ve been working hard on the more advanced chapters of Roo in Action, and as part of this I’ve amassed some background material that didn’t make it into the book.  This topic, using Spring’s Mock Entity support, was one of those topics that didn’t get a lot of attention.

In Spring Roo in Action, Chapter 3, I discuss how Roo automatically executes the Bean Validators when persisting a live entity. However, when running unit tests, we don’t have a live entity at all, nor do we have a Spring container – so how can we exercise the validation without actually hitting our Roo application and the database?

The answer is that we have to bootstrap the validation framework within the test ourselves. We can use the CourseDataOnDemand class’s getNewTransientEntityName method to generate a valid, transient JPA entity. Then, we can:

  1. Mock static entity methods, such as findById, to bring back pre-fabricated class instances of your entity
  2. Initialize the validation engine, bootstrapping a JSR-303 bean validation framework engine, and perform validation on your entity
  3. Set any appropriate properties to apply to a particular test condition
  4. Initialize a test instance of the entity validator and assert the appropriate validation results are returned

The concept in action…

Given a Student entity with the following definition:

@RooEntity
@RooJavaBean @RooToString
public class Student { @NotNull private String emergencyContactInfo; ... }

The listing below shows a unit test method that ensures the NotNull validation fires against missing emergency contact information on the Student entity:

@Test
public void testStudentMissingEmergencyContactValidation() {
  // setup our test data
  StudentDataOnDemand dod = new StudentDataOnDemand();
  // tell the mock to expect this call
  Student.findStudent(1L);
  // tell the mocking API to expect a return from the prior call in the form of
  // a new student from the test data generator, dod
  AnnotationDrivenStaticEntityMockingControl.expectReturn(
     dod.getNewTransientStudent(0));
  // put our mock in playback mode
  AnnotationDrivenStaticEntityMockingControl.playback();
  // Setup the validator API in our unit test
  LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
  validator.afterPropertiesSet();
  // execute the call from the mock, set the emergency contact field
  // to an invalid value
  Student student = Student.findStudent(1L);
  student.setEmergencyContactInfo(null);
  // execute validation, check for violations
  Set> violations =
    validator.validate(student, Default.class);
  // do we have one?
  Assert.assertEquals(1, violations.size());
  // now, check the constraint violations to check for our specific error
  ConstraintViolation violation = violations.iterator().next();
  // contains the right message?
  Assert.assertEquals("{javax.validation.constraints.NotNull.message}",
    violation.getMessageTemplate());
  // from the right field?
  Assert.assertEquals("emergencyContactInfo",
    violation.getPropertyPath().toString());
}

Analysis

The test starts with a declaration of a StudentOnDemand object, which we’ll use to generate our test data. We’ll get into the more advanced uses of the DataOnDemand Framework later in the chapter. For now, keep in mind that we can use this class to create an instance of an Entity, with randomly assigned, valid data. We then require that the test calls the Student.findStudent method, passing it a key of 1L. Next, we’ll tell the entity mocking framework that the call should return a new transient Student instance. At this point, we’ve defined our static mocking behavior, so we’ll put the mocking framework into playback mode.

Next, we issue the actual Student.findById(1L) call, this time storing the result as the member variable student. This call will trip the mock, which will return a new transient instance. We then set the emergencyContactInfo field to null, so that it becomes invalid, as it is annotated with a @NotNull annotation. Now we are ready to set up our bean validation framework.

We create a LocalValidatorFactoryBean instance, which will boot the Bean Validation Framework in the afterPropertiesSet() method, which is defined for any Spring Bean implementing InitializingBean. We must call this method ourselves, because Spring is not involved in our unit test. Now we’re ready to run our validation and assert the proper behavior has occurred.

We call our validator’s validate method, passing it the student instance and the standard Default validation group, which will trigger validation. We’ll then check that we only have one validation failure, and that the message template for the error is the same as the one for the @NotNull validation. We also check to ensure that the field that caused the validation was our emergencyContactInfo field.

In our answer callback, we can launch the Bean Validation Framework, and execute the validate method against our entity. In this way, we can exercise our bean instance any way we want, and instead of persisting the entity, can perform the validation phase and exit gracefully.

Caveats…

There are a few things slightly wrong here. First of all, the Data on Demand classes actually use Spring to inject relationships to each other, which I’ve logged a bug against as ROO-2497. You can override the setup of the data on demand class and manually create the DoD of the referring one, which is fine. They have slated to work on this bug for Roo 1.2, so it should be fixed sometime in the next few months.

Also, realize that this is NOT easy to do, compared to writing an integration test. However, this test runs markedly faster. If you have some sophisticated logic that you’ve attached to a @AssertTrue annotation, this is the way to test it in isolation.

About This Post

Did you find this post useful? Ken and Gordon both teach courses in Spring, Hibernate, Integration, Maven, Rails and more for Chariot Solutions. Visit our Education Services page for details on upcoming courses, including August’s Hibernate with Spring.