Best practices for Hibernate integration testing
I am developing a web application using Spring and Hibernate and am looking for the best practices ways to test this app using Spring. I am enjoying the support offered through AbstractTransactionalDataSourceSpringContextTests for many of my tests. What I am cuurently doing for integration testing is writing test cases like the following:
Code:
public class AbstractUserIntegrationTests extends AbstractTransactionalDataSourceSpringContextTests {
protected BizFacade bizImpl;
public void setBizFacade (BizFacade bizImpl) {
this.bizImpl= bizImpl;
}
public void testAddUser() {
// Add user
User user = new User();
user.setUsername("btucker");
user.setPassword("tuckb989");
user.setScreenName("big tucker");
user.setEmailAddress("forget@about.it");
this.bizImpl.addUser(user);
// Assert that user was added to database
List<User> users = this.bizImpl.findAllUsers();
Assert.assertEquals("Incorrect number of users found", users.size(), 1);
User loadedUser = users.get(0);
Assert.assertEquals("Username doesn't match", loadedUser.getUsername(), "btucker");
Assert.assertEquals("Password doesn't match", loadedUser.getPassword(), "tuckb989");
Assert.assertEquals("Screen name doesn't match", loadedUser.getScreenName(), "big tucker");
Assert.assertEquals("Email address doesn't match", loadedUser.getEmailAddress(), "forget@about.it");
}
}
This works great. But for linking multiple calls to Hibernate i have ran into trouble. Take for example testing the logic of deleting a user. Here is my first attempt:
Code:
public void testDeleteUser() {
// Add user
User user = new User();
user.setUsername("btucker");
user.setPassword("tuckb989");
user.setScreenName("big tucker");
user.setEmailAddress("forget@about.it");
this.bizImpl.addUser(user);
// Assert that user was added to database
List<User> users = this.bizImpl.findAllUsers();
Assert.assertEquals("Incorrect number of users found", users.size(), 1);
// Delete user
this.bizImpl.deleteUser(users.get(0));
// Assert that user has been deleted from database
users = this.bizImpl.findAllUsers();
Assert.assertEquals("Incorrect number of users found", users.size(), 0);
}
This leads to the following exception
Code:
org.hibernate.StaleStateException: Batch update returned unexpected row count from update: 0 actual row count: 0 expected: 1
If I change the deleteUser() call to from:
Code:
this.bizImpl.deleteUser(users.get(0));
to:
Code:
this.bizImpl.deleteUser(user);
the following error occurs.
Code:
org.springframework.orm.hibernate3.HibernateSystemException: a different object with the same identifier value was already associated with the session: [com.biz.domain.User#22]; nested exception is org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.biz.domain.User#22]
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session:
I understand from a previous post on this problem (http://forum.springframework.org/showthread.php?t=18793) that this happens because the object is already associated with the hibernate session.
So my question is how are people doing this? How are Spring developers handling this scenerio?
Thanks for that insight...
I was pleasantly surprised when I checked the forum and none other than Rod Johnson had posted the reply. Thanks for your response to this question and thanks for your huge contribution to the community through this framework and your books.
For those who might be having similar problems I will post the changes I made to the testDeleteUser() code.
Code:
public void testDeleteUser() {
// Add user
long id = dbHelper.addUser("btucker", "little_one1", "big tucker", "forget@about.it");
// Assert that user was added to database
List<User> users = this.bizImpl.findAllUsers();
Assert.assertEquals("Incorrect number of users found", users.size(), 1);
// Delete user
User user = new User();
user.setId(id);
this.bizImpl.deleteUser(user);
// Assert that user has been deleted from database
users = this.bizImpl.findAllUsers();
Assert.assertEquals("Incorrect number of users found", users.size(), 0);
}
Code:
public abstract class AbstractTestDatabaseHelper {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
protected abstract String getIdentityQuery();
public Long addUser(String username, String password, String screenName, String emailAddress) {
String sql = "insert into user (username, password, screenname, emailaddress) values (?, ?, ?, ?)";
Object[] args = new Object[] { username, password, screenName, emailAddress };
jdbcTemplate.update(sql, args);
return jdbcTemplate.queryForLong(getIdentityQuery());
}
public Long addTrackingDefinition(long userID, String name) {
String sql = "insert into tracking_definition (user_id, name) values (?, ?)";
Object[] args = new Object[] { userID, name };
jdbcTemplate.update(sql, args);
return jdbcTemplate.queryForLong(getIdentityQuery());
}
}
public class MySqlTestDatabaseHelper extends AbstractTestDatabaseHelper {
protected String getIdentityQuery() {
return "select last_insert_id()";
}
}