Monday, March 1, 2010

iBatis "Cache Miss", Part 2 Creating automated tests

In part 1, of this series we discussed how to set up the JBoss logging so that you can verify whether or not your iBatis caching is working. Once you get caching working, how do you prevent it from being broken by future modifications. It is important to create some automated integration tests, that will fail if the caching is broken.

You may ask does this make sense, after all iBatis is a third party Jar. Why would I want to test their code? If you can't trust third party jar files, where does the testing end. This is a valid argument. After all you would never write a test to verify that Java set/get methods work as expected. However, in the case of iBatis caching you are not so much testing third party software, although it will certainly do that, as you are testing that you configured the third party software correctly. As an added benefit, if you updated iBatis and they had broken the cacheModel, you would know immediately.

Spring provides a autowire capability for injecting beans into your test classes for integration tests. So we wanted to simulate the client behavior in an integration test. We injected the dao bean for a simple table in the database. We wrote two tests.

In the first test, we performed a get immediately that should have set up the cache. Next, making use of the Spring SimpleJdbcInsert class we bypassed our iBatis bean, to do an insert into the table. We did this so that iBatis would not flush it's cache on insert. Now if the cacheModel is set up correctly, when you go get the list of records again, it will retrieve it from the cache and it will not include this new record. Next, using the iBatis dao bean, we saved another record to this table. Now when the get all command is executed it should grow by 2 records(the one inserted through spring, and the one inserted through iBatis). Here is the test method.


public void testDatabaseCaching() {
List<LocationType> cachedTypes = _locationTypeDao.getAll();
int numTypes = cachedTypes.size();

SimpleJdbcInsert lJdbcInsert = new simpleJdbcInsert(_dataSource)
.withTableName("location_type")
.usingGeneratedKeyColumns("location_type_id");
Map<String, Object> lParameters = new HashMap<String, Object>();
lParameters.put("location_type_nm", "cacheTest");
lParameters.put("location_type_desc", "verify ibatis caching working");
lParameters.put("message_resource_key", "location.type.cacheTest");
lParameters.put("active_flag", 1);
lParameters.put("create_user", "ibatisCacheVerification@daoTest.com");
lParameters.put("create_ts", Calendar.getInstance().getTime());

// verify that the straight JDBC insert works
Number lNewId = lJdbcInsert.executeAndReturnKey(lParameters);
assertNotNull("autogen key shouldn't be null", lNewId);

// ibatis cache should be unaware that we added a new
// location type since we did it with straight JDBC
cachedTypes = _locationTypeDao.getAll();
assertEquals(numTypes, cachedTypes.size());

LocationType newType = new LocationType("cache2", "verify caching working part 2");
newType.setCreateUserName("ibatisCacheVerification@daoTest.com");
_locationTypeDao.save(newType);

// insert causes flush, so cache should now have both new types
cachedTypes = _locationTypeDao.getAll();
assertEquals(numTypes + 2, cachedTypes.size());
}

Running this first test prior to fixing the cache model resulted in assert failures, which validated that our caching was broken.

The second test was to validate a more complex caching problem. In this example, you have a location table with a foreign key to the location_type table. Performing a select on location will result in returning the location information as well as information about the location type. Suppose that the location select is cached, and then a modification occurs where some of the location type information is updated. If the cacheModel is configured incorrectly, it will not flush the cache and the location select cache will still contain the old values from the location_type table. Here is what that test looked like.

public void testIbatisCacheWithJoins() {
// get location types
List<LocationType> cachedTypes = _locationTypeDao.getAll();

// Add new location type
LocationType locType = null;
for(LocationType type : cachedTypes) {
if (type.getId() == 1) {
locType = type;
break;
}
}


// get locations to set the cache initially
_locationsDao.getLocationsByTypeId(null, locType.getId(), null);


locType.setUpdateUserName("ibatisCacheVerification@daoTest.com");
locType.setName("New Type Name");
_locationTypeDao.save(locType);
List<Location>cachedLocations =
_locationsDao.getLocationsByTypeId(null, locType.getId(), null);

assertEquals("New Type Name", cachedLocations.get(0).getLocType());
}

Running this test case with the cacheModel set up incorrectly resulted in an assert failure as well. Now that we have the two main tests in place we needed to fix the CacheModel definition, which is discussed in the final blog, iBatis "Cache Miss", Part 3.

No comments: