Case Study

Achieving Stateless Transactional Testing

Previous
The Case Study addresses the problem of stateless testing of transactional operations by introducing compensating transactions. So, at the beginning of the test, objects on which there are dependencies are inserted transactionally into the database, the transacational operations are executed, and then at the conclusion of the test, all objects created by the test are removed transactionally. Here is the important code from PetStoreImplTransactionTest. PetStoreImplTransactionTest.java (excerpt)
public class PetStoreImplTransactionTest extends SpringUnitTest {
	
	protected void onSetUp() throws Exception {
		Map<String, List<? extends Object>> prepopulated = (Map<String, List<? extends Object>>)getObject("prepopulated");
		if (prepopulated != null && prepopulated.size() > 0) {
			List<? extends Object> categories = prepopulated.get("categories");
			if (categories != null) {
				getAdministrationService().createCategories((List<? extends Category>)categories);
			}
			List<? extends Object> accounts = prepopulated.get("accounts");
			if (accounts != null) {
				getAdministrationService().createAccounts((List<? extends Account>)accounts);
			}
			List<? extends Object> products = prepopulated.get("products");
			if (products != null) {
				getAdministrationService().createProducts((List<? extends Product>)products);
			}
			List<? extends Object> items = prepopulated.get("items");
			if (items != null) {
				getAdministrationService().createItems((List<? extends Item>)items);
			}
			List<? extends Object> orders = prepopulated.get("orders");
			if (orders != null) {
				getAdministrationService().createOrders((List<? extends Order>)orders);
			}
		}
	}
	
	protected void onTearDown() throws Exception {
		getAdministrationService().deleteOrders();
		getAdministrationService().deleteItems();
		getAdministrationService().deleteProducts();
		getAdministrationService().deleteAccounts();
		getAdministrationService().deleteCategories();
		setDirty();
	}
	
}
To begin with, notice that this test extends SpringUnitTest, not SpringUnitTransactionalTest. Allowing that perhaps the choice of class name is open to question, SpringUnitTransactionalTest rolls back transactions, which is not the desired behavior here. Remember also that the methods intended to be overriden by subclasses are onSetUp and onTearDown. The Case Study uses onSetUp to create all objects needed to seed the database. Notice that it uses methods provided by the AdministrationService to accomplish this. onTearDown is called after the body of the test is completed, and here, the Case Study restores the original database state by calling the AdministrationService to delete objects. The call to setDirty at the conclusion of onTearDown illustrates a fairly subtle interaction between Spring and JUnit. In Spring version 1.x, there are two object scopes, singleton and prototype. Prototype scope is appropriate for stateless objects, singleton scope is appropriate for stateful, shared objects. For unit tests, the SpringUnit context is itself a singleton bean that contains a map holding various test data inputs and expected values. The Spring framework avoids rebuilding this structure with each JUnit test execution in order to improve efficiency. This behavior can be overriden by calling setDirty. In JUnit tests, the values of singletons are shared across executions of each test method. Normally, this is a bad practice since it makes tests stateful, potentially order-dependent, and often simply incorrect. Yet here we have objects that must be created as singletons in order to have stateful objects that have shared references. Calling the setDirty method works around this contradiction by forcing a refresh the SpringUnit context with the execution of each JUnit test method. Naturally, there is a performance penalty associated with this, but since this is a practice to be used for integration tests, which are assumed to be slower anyway, this may be viewed as an acceptable consequence. Note that the call to setDirty is made by the framework (SpringUnitTransactionalTest) for rolled-back transactions of the data access layer.
Previous