Creating integration tests for persistent entities

Spring Roo provides a test integration command that simplifies the creation of integration tests for persistent entities. In this recipe, we'll look at how to create an integration test for an entity.

Getting ready

Exit the Roo shell and delete the contents of the C: oo-cookbookch02-recipes directory.

Execute the ch02_jsr303_fields.roo script. It creates a flight-app Roo project and sets up Hibernate as persistence provider using the persistence setup command. The script also creates a Flight entity, which has FlightKey as its composite primary key class, and adds fields to the Flight and FlightKey classes. If you are using a different database than MySQL or your connection settings are different from what is specified in the script, then modify the script accordingly.

Start the Roo shell from the C: oo-cookbookch02-recipes directory.

How to do it...

The following steps will show you how to create integration tests:

  1. Change the focus of the Roo commands to the Flight entity:
    roo> focus --class ~.domain.Flight
    
  2. Execute the test integration command:
    ~.domain.Flight> test integration
    Created ...FlightDataOnDemand.java
    Created ...FlightIntegrationTest.java
    Created ...FlightDataOnDemand_Roo_DataOnDemand.aj
    Created ...FlightIntegrationTest_Roo_IntegrationTest.aj
    Created ... FlightDataOnDemand_Roo_Configurable.aj
    Created ... Flight_Roo_Configurable.aj
    

How it works...

The test integration command generates files in the src estjava folder. The output from the test integration command shows that the following files are generated:

  • FlightDataOnDemand.java: represents a 'data on demand' class which provides the necessary data for automated integration testing of the Flight entity.
  • FlightDataOnDemand_Roo_DataOnDemand.aj: this AspectJ ITD file defines methods that are added to the FlightDataOnDemand class during compilation. The methods defined in this AspectJ ITD file are responsible for dynamically creating 10 instances of the Flight entity and storing them in the database—referred to as seed data. These Flight instances are used while performing integration testing. The entity instances created by AspectJ ITD comply with the JSR 303 constraints that apply on persistent fields of the entity. By default, a transaction associated with a test method is rolled-back after the test method completes—the reason why you won't see seed data in database tables after the execution of integration tests. Refer to the Executing persistent entities tests recipe to see an example usage of Spring's @Roolback annotation to specify that transactions associated with test methods must not be rolled-back.
  • FlightIntegrationTest.java: represents the JUnit integration test class for the Flight entity.
  • FlightIntegrationTest_Roo_IntegrationTest.aj: AspectJ ITD responsible for defining integration testing methods for the Flight entity.
  • FlightDataOnDemand_Roo_Configurable.aj and Flight_Roo_Configurable.aj: AspectJ ITDs that add the @Configurable annotation to FlightDataOnDemand and Flight classes, respectively.

Let's now look at each of these files in detail.

The following listing shows the FlightDataOnDemand.java class:

import org.springframework.roo.addon.dod.RooDataOnDemand;
import sample.roo.flightapp.domain.Flight;

@RooDataOnDemand(entity = Flight.class)
public class FlightDataOnDemand { }

The code listing shows the use of Roo's @RooDataOnDemand annotation, which identifies the persistent entity for which the FlightDataOnDemand class creates seed data for integration testing. The @RooDataOnDemand annotation is responsible for the creation of the corresponding *_Roo_DataOnDemand.aj AspectJ ITD file. The @RooDataOnDemand accepts two attributes to customize the behavior of seed data generation:

  • entity: identifies the persistent entity for which the seed data needs to be created.
  • quantity: the number of records to be created for the entity, default being 10. If you want to create more records for integration testing of the entity, then specify an appropriate value of this attribute.

If you are using a performance testing tool like JMeter to test the performance of the JPA layer of your enterprise application, you can modify the FlightIntegrationTest JUnit test class and use it as a JUnit Request Sampler (or you can put a wrapper around FlightIntegrationTest and use Java Request Sampler) when creating a test plan in JMeter. This lets you quickly get started with testing the performance of your data access code. Based on the performance test requirements for a persistent entity, you can adjust the value of the quantity attribute of the @RooDataOnDemand annotation. For instance, if you want to test the performance of the data access layer when there are n number of records in the database, then specify the value of quantity attribute as n.

The following listing shows the FlightDataOnDemand_Roo_DataOnDemand.aj AspectJ ITD file, which defines methods, attributes, and annotations that are weaved into the FlightDataOnDemand.java class at compile-time:

privileged aspect FlightDataOnDemand_Roo_DataOnDemand {
    
  declare @type: FlightDataOnDemand: @Component;
    
  private Random FlightDataOnDemand.rnd = 
                   new java.security.SecureRandom();
    
  private List<Flight> FlightDataOnDemand.data;
    
  public Flight FlightDataOnDemand.getNewTransientFlight (int  
    index) {...}
    
  public Flight FlightDataOnDemand.getSpecificFlight(int 
    index) {...}
    
  public Flight FlightDataOnDemand.getRandomFlight() {...}
    
  public boolean FlightDataOnDemand.modifyFlight(Flight obj)
  {...}
    
  public void FlightDataOnDemand.init() {...}
    
}

The code listing shows that AspectJ ITD does the following:

  • Adds the @Component annotation to the FlightDataOnDemand class, so that it is auto-registered with Spring's application context. This enables you to create custom integration tests in which you can autowire one or more *DataOnDemand classes.
  • Creates an instance of the java.security.SecureRandom class, which is used for generating a random number.
  • Declares a list which holds Flight entities generated by the 'data on demand' class. These Flight entity instances represent the seed data generated by the 'data on demand' class.
  • Defines a getNewTransientFlight(int index) method for generating a unique Flight instance based on the value of the index argument. The getNewTransientFlight method creates persistent entity instances which comply with the JSR 303 annotations specified for the entity's persistent fields. For instance, the Flight entity specifies the @Size(min = 3, max = 20) JSR 303 annotation for the origin and destination fields (refer to the Flight.java class); therefore, the getNewTransientFlight method attempts (that is, it is not guaranteed, as we'll see soon) to ensure that the size of the origin and destination fields comply with the corresponding JSR 303 annotation.
  • Defines the getSpecificFlight(int index) method, which returns the Flight entity at the specified index from the collection of seed data maintained by the 'data on demand' class.
  • Defines the getRandomFlight() method, which returns Flight entity at a random index (obtained from the java.security.SecureRandom instance) in the seed data collection maintained by the 'data on demand' class.
  • Defines the modifyFlight(Flight+obj) method, which is supposed to modify the Flight entity passed as argument and return the success or failure of modification. But it simply returns false, that is, it never modifies the passed Flight instance.
  • Defines an init() method, which is responsible for creating the seed data for integration testing of the Flight entity. It creates Flight entities in the database using the getNewTransientFlight(int index) method. The number of Flight entities created in the database is determined by the value of the quantity attribute of the @RooOnDemand annotation.

Note

It is important to note that the init() method is internally called by methods defined in the *_Roo_DataOnDemand.aj to ensure that a fresh set of seed data is created in the database each time a test method is invoked.

Tip

JSR 303 annotations and seed data

The 'data on demand' classes generated by Spring Roo provide limited support for creating entity instances that comply with JSR 303 annotations. As of Spring Roo 1.1.3, it only supports @NotNull, @Past, and @Future JSR 303 constraints, along with some support for maximum and minimum range annotations. If your project uses any other JSR 303 annotation, then it is recommended to create your own setter method for entity fields in the *DataOnDemand.java class.

The following listing shows the FlightIntegrationTest.java class:

import org.junit.Test;
import org.springframework.roo.addon.test.RooIntegrationTest;

@RooIntegrationTest(entity = Flight.class )
public class FlightIntegrationTest {
    @Test
    public void testMarkerMethod() {
    }
}

The code listing shows the presence of Roo's @RooIntegrationTest annotation, which indicates that an integration test AspectJ ITD is to be created for the Flight entity. The testMarkerMethod is an example JUnit test method. The @RooIntegrationTest annotation accepts an entity attribute, which identifies the persistent entity for which the integration test is created—Flight in case of the FlightIntegrationTest class. Additionally, the @RooIntegrationTest annotation defines attributes which let you control the integration test methods that are auto-generated by Roo in the corresponding *_Roo_IntegrationTest.aj AspectJ ITD.

The following listing shows the FlightIntegrationTest_Roo_IntegrationTest.aj AspectJ ITD file that was generated by Roo corresponding to the FlightIntegrationTest class:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.   
       junit4.SpringJUnit4ClassRunner;
privileged aspect FlightIntegrationTest_Roo_IntegrationTest {

 declare @type: FlightIntegrationTest:      
       @RunWith(SpringJUnit4ClassRunner.class);

 declare @type: FlightIntegrationTest: 
    @ContextConfiguration(locations = 
    "classpath:/META-INF/spring/applicationContext.xml");

  declare @type: FlightIntegrationTest:@Transactional;
    
    @Autowired
    private FlightDataOnDemand FlightIntegrationTest.dod;
    
    @Test
    public void FlightIntegrationTest.testFindAllFlights() {
    ...
    }

    @Test
    public void FlightIntegrationTest.testPersist() {
    ...
    }
    ...
}

The previous code shows that AspectJ ITD does the following:

  • Adds JUnit's @RunWith annotation to the FlightIntegrationTest class, instructing the use of Spring's SpringJUnit4ClassRunner for running the JUnit tests.
  • Adds Spring's @ContextConfiguration annotation to the FlightIntegrationTest class, which specifies the location of Spring's application-context XML file to be used for executing the tests. By default, it is set to use the applicationContext.xml file in the META-INFspring folder. If you want to use a different application-context XML for running the tests, then specify the @ContextConfiguration annotation in the FlightIntegrationTest class.
  • Adds Spring's @Transactional annotation to the FlightIntegrationTest class, which means all the test methods defined in FlightIntegrationTest (or weaved into it by AspectJ ITD) are transactional in nature.
  • Declares integration test methods, like testFindAllFlights, testPersist, and so on. The test methods make use of FlightDataOnDemand (a 'data on demand' class) instance for creating Flight instances (the seed data) for testing the Flight entity and for retrieving a random Flight instance from the database.

There's more...

In some scenarios you may want to customize the seed data created by *DataOnDemand.java class and to control the integration test methods that are auto-generated by Spring Roo.

Customizing seed data creation

If you're using JSR 303 annotations that are not supported by Spring Roo, you'll need to create custom setter methods (defined in *_Roo_DataOnDemand.aj) for setting persistent entity field values. The following listing shows some of the setter methods (setNumOfSeats and setOrigin) auto-generated by Roo for the Flight entity; the following is the FlightDataOnDemand_Roo_DataOnDemand.aj method:

public Flight FlightDataOnDemand.getNewTransientFlight
(int index) {
  sample.roo.flightapp.domain.Flight obj = new 
                  sample.roo.flightapp.domain.Flight();
  setEmbeddedId(obj, index);
  setNumOfSeats(obj, index);
  setOrigin(obj, index);
  setDestination(obj, index);
  ...
  return obj;
}

private void FlightDataOnDemand.setEmbeddedId(Flight obj, 
 int index) {
  java.lang.String flightId = "flightId_" + index;
  ...
  obj.setId(embeddedIdClass);
}

private void FlightDataOnDemand.setNumOfSeats(Flight obj, 
 int index) {
  java.lang.Integer numOfSeats = new Integer(index);
  obj.setNumOfSeats(numOfSeats);
}

private void FlightDataOnDemand.setOrigin(Flight obj, 
 int index) {
  java.lang.String origin = "origin_" + index;
  if (origin.length() > 20) {
    origin = origin.substring(0, 20);
  }
  obj.setOrigin(origin);
}
...

In the code listing, there are a couple of things to notice about the Flight entity that is created:

  • @NotNull JSR 303 annotation is taken care of while creating the Flight entity, but the @DecimalMax and @DecimalMin JSR 303 annotations on the numOfSeats field are completely ignored (refer setNumOfSeats method in the code listing).
  • The @Size annotation on origin and destination fields (refer setOrigin method in the code listing) is partially supported as the method only checks if the maximum length of the value assigned to destination (or origin) field is 20. It doesn't check for the minimum length as 3.
  • The composite key is dynamically created and set (refer to the setEmbeddedId method in the previous listing).

To address the issue with JSR 303 support in the auto-generated Flight entity instance by FlightDataOnDemand_Roo_DataOnDemand.aj, we can write custom setNumOfSeats, setOrigin, and setDestination methods in the FlightDataOnDemand.java class, which does the following:

  • Checks that the length of origin and destination fields are within the limits defined by @Size
  • Provides checks for the @DecimalMin and @DecimalMax JSR 303 annotations

It is important to note that if you create setNumOfSeats, setOrigin, and setDestination methods in the FlightDataOnDemand.java class, then Roo removes these methods from the corresponding FlightDataOnDemand_Roo_DataOnDemand.aj AspectJ ITD. This requires that the signature of methods in the FlightDataOnDemand.java class is the same as the signature of methods defined in the corresponding FlightDataOnDemand_Roo_DataOnDemand.aj AspectJ ITD file. Similarly, you can customize any method of FlightDataOnDemand_Roo_DataOnDemand.aj by writing them in the FlightDataOnDemand.java class.

The following figure shows how Roo removes setNumOfSeats, setOrigin, and setDestination methods from the FlightDataOnDemand_Roo_DataOnDemand.aj file:

Customizing seed data creation

The figure shows that Spring Roo Shell monitors the FlightDataOnDemand.java class (because it is annotated with Roo's @RooDataOnDemand annotation). When any change is made to the FlightDataOnDemand.java class, Roo triggers add-on(s) responsible for managing the AspectJ ITD file(s) corresponding to the FlightDataOnDemand.java class. In the case of FlightDataOnDemand, Roo triggers Dod add-on ('data on demand') to update FlightDataOnDemand_Roo_DataOnDemand.aj AspectJ ITD, so that the AspectJ ITD is in sync with the FlightDataOnDemand.java class. When you add setNumOfSeats, setOrigin, and setDestination methods in the FlightDataOnDemand.java class, Dod add-on checks if the methods already exist there. If they exist, Dod add-on removes those methods from the FlightDataOnDemand_Roo_DataOnDemand.aj AspectJ ITD.

It is important to note that modifying a Java class that is annotated with Roo annotations may result in multiple AspectJ ITDs getting affected. For instance, if you remove the modifiedBy field from the Flight.java class, Roo will update the Flight_Roo_JavaBean.aj, Flight_Roo_ToString.aj, and FlightDataOnDemand_Roo_DataOnDemand.aj AspectJ ITD files to reflect the removal of the modifiedBy field.

Controlling integration test methods

We mentioned earlier that the @RooIntegrationTest annotation in *IntegrationTest.java defines attributes which let you control the integration test methods that are auto-generated by Roo in the *_Roo_IntegrationTest.aj AspectJ ITD file. The @RooIntegrationTest annotation defines the following attributes to control the auto-generation of integration test methods: count, find, findAll, findAllMaximum, findEntries, flush, merge, persist, and remove. If the value of any of these attributes is specified as false, then the corresponding test method is removed from the *_Roo_IntegrationTest.aj AspectJ ITD file. For instance, the testFindAllFlights method searches for all Flight instances in the database, which may not be desirable for performance reasons. To instruct Spring Roo to remove the auto-generated testFindAllFlights method from the FlightIntegrationTest_Roo_IntegrationTest.aj file, all you need to do is to specify the value of the findAll attribute value as false in the @RooIntegrationTest annotation, as shown here:

@RooIntegrationTest(entity = Flight.class, findAll=false)
public class FlightIntegrationTest {
   ...
}

Generating integration tests at the time of entity creation

In this recipe we saw how to create integration tests using the test integration command. You can also use the testAutomatically argument of the entity command to instruct Roo to create integration tests at the time of entity creation, as shown here:

roo> entity --class ~.domain.Flight --testAutomatically --identifierType ~.domain.FlightKey --table FLIGHT_TBL

Providing custom implementation for integration tests

As with the 'data on demand' class, you can provide a custom implementation for an integration test method in your *IntegrationTest.java class. For instance, if you want to modify the testPersist Roo-generated test method with a customized testPersist method, create a testPersist method in the FlightIntegrationTest.java file. Adding the testPersist method to *IntegrationTest.java results in the removal of the testPersist method from the corresponding *_Roo_IntegrationTest.aj file by Roo. Similarly, you can customize any other test method defined in the *_Roo_IntegrationTest.aj file.

See also

  • Refer to the Creating new 'data on demand' for testing entities recipe to see how you can create seed data for an entity
  • Refer to the Executing persistent entities tests recipe to see how integration and mock tests of entities are executed using Spring Roo
  • Refer to the Creating mock tests for persistent entities recipe to create mock static methods defined in entities
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.114.19