Announcement Announcement Module
Collapse
No announcement yet.
Best practices for Hibernate integration testing Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • 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("[email protected]");		
    		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(), "[email protected]");				
    	}
    }
    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("[email protected]");		
    		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?
    Last edited by robyn; May 14th, 2006, 06:04 PM.

  • #2
    Remember that you can clear the Hibernate session, removing objects already associated with it. This is often necessary before requerying in tests, and solves most (if not all) problems.

    I typically use JDBC for verification. The pattern is
    - do Hibernate operation
    - flush Hibernate session
    - issue JDBC query to verify results

    That way I'm verifying what Hibernate did to the database in the same transaction.

    Comment


    • #3
      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&#40;&#41; &#123;
      		// Add user
              long id = dbHelper.addUser&#40;"btucker", "little_one1", "big tucker", "[email protected]"&#41;;
              
      		// Assert that user was added to database
      		List<User> users = this.bizImpl.findAllUsers&#40;&#41;;		
      		Assert.assertEquals&#40;"Incorrect number of users found", users.size&#40;&#41;, 1&#41;;        
      		
      		// Delete user
      		User user = new User&#40;&#41;;
      		user.setId&#40;id&#41;;
      		this.bizImpl.deleteUser&#40;user&#41;;
      		
      		// Assert that user has been deleted from database
      		users = this.bizImpl.findAllUsers&#40;&#41;;	
      		Assert.assertEquals&#40;"Incorrect number of users found", users.size&#40;&#41;, 0&#41;;	
      	&#125;
      Code:
      public abstract class AbstractTestDatabaseHelper &#123;
      
          private JdbcTemplate jdbcTemplate;
          
      	public void setJdbcTemplate&#40;JdbcTemplate jdbcTemplate&#41; &#123;
      		this.jdbcTemplate = jdbcTemplate;
      	&#125;
      
      	protected abstract String getIdentityQuery&#40;&#41;;
          
          public Long addUser&#40;String username, String password, String screenName, String emailAddress&#41; &#123;
              String sql = "insert into user &#40;username, password, screenname, emailaddress&#41; values &#40;?, ?, ?, ?&#41;";
              Object&#91;&#93; args = new Object&#91;&#93; &#123; username, password, screenName, emailAddress &#125;;
              jdbcTemplate.update&#40;sql, args&#41;;
              return jdbcTemplate.queryForLong&#40;getIdentityQuery&#40;&#41;&#41;;
          &#125;	
          
          public Long addTrackingDefinition&#40;long userID, String name&#41; &#123;
              String sql = "insert into tracking_definition &#40;user_id, name&#41; values &#40;?, ?&#41;";
              Object&#91;&#93; args = new Object&#91;&#93; &#123; userID, name &#125;;
              jdbcTemplate.update&#40;sql, args&#41;;
              return jdbcTemplate.queryForLong&#40;getIdentityQuery&#40;&#41;&#41;;
          &#125;    
      &#125;
      
      public class MySqlTestDatabaseHelper extends AbstractTestDatabaseHelper &#123;
      
      	protected String getIdentityQuery&#40;&#41; &#123;
      		return "select last_insert_id&#40;&#41;";
      	&#125;
      	
      &#125;

      Comment


      • #4
        As I flesh out the business logic for this application I am presented with another problem. I have put logic in place to handle adding users with duplicate usernames, screen names, or email addresses. I check this by adding the business logic to the domain facade similar to jpetstore's PetStoreImpl.

        But when I attempt to unit test (using AbstractTransactionalDataSourceSpringContextTests) Hibernate seems to not correctly handle the multiple calls to check for duplicates.

        The DuplicateUsernameException gets thrown when running the testUpdateUser().

        When debugging in Eclipse I have noticed that this call
        Code:
        User usernameUser = this.userDAO.findUserByUsername&#40;user.getUsername&#40;&#41;&#41;;
        returns the correct user but with an incorrect id. Hence the next check
        Code:
        		if &#40;usernameUser != null && usernameUser.getId&#40;&#41; != user.getId&#40;&#41;&#41;
        			throw new DuplicateUsernameException&#40;user.getUsername&#40;&#41;&#41;;
        throws the exception because it thinks there is already another user with this username.

        Any ideas? How are others testing there domain logic?

        Snip of Biz Facade
        Code:
        public class BizImpl implements BizFacade &#123;
        
        	private UserDAO userDAO;
        	
        	public void setUserDAO&#40;UserDAO userDAO&#41; &#123;
        		this.userDAO = userDAO;
        	&#125;
        
        	public User updateUser&#40;User user&#41; &#123;
        		// Check for duplicate username
        		User usernameUser = this.userDAO.findUserByUsername&#40;user.getUsername&#40;&#41;&#41;;
        		if &#40;usernameUser != null && usernameUser.getId&#40;&#41; != user.getId&#40;&#41;&#41;
        			throw new DuplicateUsernameException&#40;user.getUsername&#40;&#41;&#41;;	
        		
        		// Check for duplicate screen name
        		User screenNameUser = this.userDAO.findUserByScreenName&#40;user.getScreenName&#40;&#41;&#41;;
        		if &#40;screenNameUser != null && screenNameUser.getId&#40;&#41; != user.getId&#40;&#41;&#41;
        			throw new DuplicateScreenNameException&#40;user.getScreenName&#40;&#41;&#41;;	
        				
        		// Check for duplicate email address
        		User emailUser = this.userDAO.findUserByEmail&#40;user.getEmailAddress&#40;&#41;&#41;;
        		if &#40;emailUser != null && emailUser.getId&#40;&#41; != user.getId&#40;&#41;&#41;
        			throw new DuplicateEmailAddressException&#40;user.getEmailAddress&#40;&#41;&#41;;	
        		
        		return this.userDAO.update&#40;user&#41;;
        	&#125;	
        &#125;
        Snip of relevant test case
        Code:
        public class BaseUserIntegrationTest extends AbstractTransactionalDataSourceSpringContextTests &#123;
        	
        	protected BizFacade bizImpl;
        	protected AbstractTestDatabaseHelper dbHelper;
        	
        	public void setBizImpl&#40;BizFacade bizImpl&#41; &#123;
        		this.bizImpl = bizImpl;
        	&#125;	
        	public void setDbHelper&#40;AbstractTestDatabaseHelper dbHelper&#41; &#123;
        		this.dbHelper = dbHelper;
        	&#125;
        
            protected void onSetUpInTransaction&#40;&#41; throws Exception &#123;
                this.dbHelper.setJdbcTemplate&#40;this.jdbcTemplate&#41;;
            &#125;
        
            protected void onTearDownInTransaction&#40;&#41; throws Exception &#123;
                super.onTearDownInTransaction&#40;&#41;;
                clearCache&#40;&#41;;
            &#125;
        
            private void clearCache&#40;&#41; &#123;
            	SessionFactory sessionFactory = &#40;SessionFactory&#41;applicationContext.getBean&#40;"sessionFactory"&#41;;
        
                sessionFactory.evict&#40;User.class&#41;;
            &#125;
        
        	public void testUpdateUser&#40;&#41; &#123;
        		// Add user
                long userID = dbHelper.addUser&#40;"btucker", "little_one1", "big tucker", "[email protected]"&#41;;
                		
        		// Update user with new username, password, and screen name
        		User userToUpdate = this.bizImpl.findUserByID&#40;userID&#41;;	
        		userToUpdate.setUsername&#40;"queen_bee"&#41;;
        		userToUpdate.setPassword&#40;"4509890"&#41;;
        		userToUpdate.setScreenName&#40;"yakami"&#41;;
        		userToUpdate.setEmailAddress&#40;"iamtheman@forget_about.it"&#41;;		
        		
        		this.bizImpl.updateUser&#40;userToUpdate&#41;;		
        
        		// Assert that user updated in database
        		User updatedUser = this.bizImpl.findUserByID&#40;userToUpdate.getId&#40;&#41;&#41;;				
        		
        		Assert.assertEquals&#40;"Username not updated", updatedUser.getUsername&#40;&#41;, "queen_bee"&#41;;
        		Assert.assertEquals&#40;"Password not updated", updatedUser.getPassword&#40;&#41;, "4509890"&#41;;
        		Assert.assertEquals&#40;"Screen name not updated", updatedUser.getScreenName&#40;&#41;, "yakami"&#41;;
        		Assert.assertEquals&#40;"Email address not updated", updatedUser.getEmailAddress&#40;&#41;, "iamtheman@forget_about.it"&#41;;
        	&#125;	
        &#125;
        Snip of Hibernate DAO
        Code:
        public class HibernateUserDAO extends GenericHibernateDAO<User, Long> implements UserDAO &#123;
        
        	public HibernateUserDAO&#40;&#41; &#123;
        		super&#40;User.class&#41;;
        	&#125;
        
        	public User findUserByUsername&#40;String username&#41; throws DataAccessException &#123;
                Query q = this.getSession&#40;&#41;.createQuery&#40;"from User u where u.username=&#58;username"&#41;;
                q.setParameter&#40;"username", username&#41;;
                return &#40;User&#41; q.uniqueResult&#40;&#41;;		
        	&#125;
        &#125;

        Comment

        Working...
        X