Announcement Announcement Module
Collapse
No announcement yet.
Mocking SimpleJdbcTemplate Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Mocking SimpleJdbcTemplate

    Hi,

    I was hoping someone could help me with mocking SimpleJdbcTemplate.

    In my DAO code, I call the SimpleJdbcTemplate.batchUpdate( String sql, List<Object[]> params ) method.

    In my test case, I attempted to mock the SimpleJdbcTemplate using the EasyMock class extension (version 2.5.2). However, it seems that when I run the test case, it simply ignores this and expects all the calls to Connection, PreparedStatement, etc. I've been having trouble with those, so I was hoping I could bypass all of it by mocking SimpleJdbcTemplate instead.

    I explicitly use a call to the EasyMock class extension as I also use the regular EasyMock in some of my other test cases.

    Below are the relevant lines of code in my unit test:

    Code:
    import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
    import org.easymock.classextension.EasyMock;
    
    public class MyDAOTest {
    
        /** Reference to mock SimpleJdbcTemplate. */
        private SimpleJdbcTemplate mockSimpleJdbcTemplate;
    
        @Before
        public void setUp(){
            
            myDAO                = new MyDAO();
            mockSimpleJdbcTemplate = org.easymock.classextension.EasyMock.
                createStrictMock( SimpleJdbcTemplate.class );
        }
    
        @Test
        public void testInsertQueries(){
    
            try{
                      
                //MY_QUERY is a valid query declared as a constant
                //myParameters is a valid, non-empty List<Object[]>.
                org.easymock.classextension.EasyMock.expect( mockSimpleJdbcTemplate.
                    batchUpdate( MY_QUERY, myParameters ) ).
                    andReturn( new int[0] );
                
                org.easymock.classextension.EasyMock.replay( mockSimpleJdbcTemplate );
    
                //call the DAO which has multiple batchUpdate calls
                myDAO.insertQueries();
            }
            catch( Exception ex ){
                fail();
            }
        }
    }
    Below is the failure trace:
    Code:
    java.lang.AssertionError: 
      Unexpected method call getConnection():
        getConnection(): expected: 1, actual: 2
    	at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:43)
    	at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:72)
    	at $Proxy6.getConnection(Unknown Source)
    	at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:113)
    	at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:79)
    	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:572)
    	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:614)
    	at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:858)
    	at org.springframework.jdbc.core.BatchUpdateUtils.executeBatchUpdate(BatchUpdateUtils.java:32)
    	at org.springframework.jdbc.core.simple.SimpleJdbcTemplate.batchUpdate(SimpleJdbcTemplate.java:257)
    	at org.springframework.jdbc.core.simple.SimpleJdbcTemplate.batchUpdate(SimpleJdbcTemplate.java:253)
    	at project.dao.MyDAO.updateWithBatch(MyDAO.java:339)
    	at ingestor.dao.MyDAO.insertQueries(MyDAO.java:286)
    	at ingestor.dao.MyDAOTest.testInsertQueries(MyDAOTest.java:1482)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    	at java.lang.reflect.Method.invoke(Unknown Source)
    	at org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99)
    	at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81)
    	at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
    	at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75)
    	at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45)
    	at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:66)
    	at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35)
    	at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42)
    	at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
    	at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52)
    	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:45)
    	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
    Any help would be greatly appreciated!

    Thanks.

  • #2
    That means it is expecting you to mock that method.

    To give you an idea.. Here's my code.. This should help you.
    Code:
    <<DaoImpl.java>>
    public class DAOImpl extends JdbcDaoSupport implements DAO {
    	
    
    	public static final String SELECT_QUERY = 
    		"select * from TABLE_NAME where id = ?";
        
    	private static final Logger logger = Logger.getLogger(DAOImpl.class);
        
        public ResultSetExtractor resultSetExtractorImpl;
        
        public DAOImpl() {
        	resultSetExtractorImpl = new ResultSetExtractorImpl();
        }
    
    
        @Transactional(propagation=Propagation.REQUIRED, readOnly=true)
    	@SuppressWarnings("unchecked")
    	public Map<String, Map<String, BigInteger>> getWorkingFields(String id) throws Exception {
    		Map<String, Map<String, BigInteger>> map = null;
    		try {
    			map =  (Map<String, Map<String, BigInteger>>)getJdbcTemplate().query(SELECT_QUERY, new Object[] {id}, resultSetExtractorImpl);
    		} catch (Exception exp) {
    			logger.error("SQLException caught while extracting Data for id, " + id +" and Exception is:"+exp);
    			throw exp;
    		} 
    		return map;
    	}
    	
    	class ResultSetExtractorImpl implements ResultSetExtractor {
    
    		public Map<String, Map<String, BigInteger>> extractData(ResultSet rs) throws SQLException,
    				DataAccessException {
    			
    			Map<String, Map<String, BigInteger>> map = new HashMap<String, Map<String, BigInteger>>();
    			
    			try {
    			
    			while (rs.next()) {
    				...
    			}
    			} catch (SQLException exp) {
    				logger.error("SQLException caught while extracting Data, ", exp);
    				throw exp;
    			} catch (DataAccessException exp) {
    				logger.error("DataAccessException caught while extracting Data, ", exp);
    				throw exp;
    			}			
    			
    			return map;
    		}
    		
    	}
    	
    	
    }
    
    
    <<DaoImplTest.java>>
    
    
    import static org.easymock.EasyMock.expect;
    import static org.easymock.EasyMock.expectLastCall;
    import static org.easymock.classextension.EasyMock.createMock;
    import static org.easymock.classextension.EasyMock.replay;
    import static org.easymock.classextension.EasyMock.verify;
    
    import java.math.BigInteger;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.sql.DataSource;
    
    import junit.framework.TestCase;
    
    import org.junit.Before;
    import org.junit.Test;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.ResultSetExtractor;
    
    
    public class DAOImplTest extends TestCase {
    	public static String PENDING = PENDING;
    	public static String ID_REF = "idref";
    	public static String ORDER_ID = "id";
    	
        private DAOImpl daoImpl;
        private Map<String, Map<String, BigInteger>> map;
        private ResultSetExtractor mockResultSetExtractor;
    	private JdbcTemplate jdbcTemplate;
    	private DataSource mockDataSource;
    	private Connection mockConnection;
    	private ResultSet mockResultSet;
    	private PreparedStatement mockPrepareStatement;
    	private static String id = "65";
    
    
        @Before
        public void setUp() throws Exception{
    
        	super.setUp();
        	daoImpl = new DAOImpl();
        	mockDataSource = createMock(DataSource.class);
        	mockConnection = createMock(Connection.class);
        	mockResultSet = createMock(ResultSet.class);
        	mockPrepareStatement = createMock(PreparedStatement.class);
        	jdbcTemplate = new JdbcTemplate(mockDataSource);
        	mockResultSetExtractor = createMock(ResultSetExtractor.class);
        	daoImpl.setJdbcTemplate(jdbcTemplate);
        	daoImpl.resultSetExtractorImpl = mockResultSetExtractor;
    		map = new HashMap<String, Map<String, BigInteger>>();
        	Map<String, BigInteger> values = new HashMap<String, BigInteger>();
        	values.put(DAO.PENDING, new BigInteger("123456"));    
        	map.put(DAO.PENDING, values);
    
        }
        
        @SuppressWarnings("unchecked")
    	@Test
    	public void testExtractData() throws Exception {
    
        	ResultSet mockResultSet = createMock(ResultSet.class);
    		expect(mockResultSetExtractor.extractData(mockResultSet)).andReturn(map);
    		replay(mockResultSet, mockResultSetExtractor);
    
    		Object retVal = daoImpl.resultSetExtractorImpl.extractData(mockResultSet);
    		assertEquals(map.getClass(), retVal.getClass());
    		Map<String, Map<String, BigInteger>> result = (Map<String, Map<String, BigInteger>>)retVal;
    		assertEquals(map, result);
    		verify(mockResultSet, mockResultSetExtractor);
    
    	}
    	
    	
        @Test
    	public void testGetNoOrders() throws Exception {
        	
        	expect(mockDataSource.getConnection()).andReturn(mockConnection);
        	expect(mockConnection.prepareStatement(DAOImpl.SELECT_QUERY)).andReturn(mockPrepareStatement);
        	mockPrepareStatement.setString(1, id);
        	expectLastCall().times(1);
        	expect(mockPrepareStatement.getWarnings()).andReturn(null);
        	expect(mockPrepareStatement.executeQuery()).andReturn(null);
        	expect(mockResultSetExtractor.extractData(null)).andReturn(null);
    		replay(mockResultSet, mockPrepareStatement, mockConnection, mockDataSource, mockResultSetExtractor);
    
    		Map<String, Map<String, BigInteger>> result = daoImpl.getWorkingFields(id);
    		assertEquals(null, result);
    		verify(mockResultSet, mockPrepareStatement, mockConnection, mockDataSource, mockResultSetExtractor);
    
    	}
        
        @Test
    	public void testGetWorkingOrders() throws Exception {
    
        	expect(mockDataSource.getConnection()).andReturn(mockConnection);
        	expect(mockConnection.prepareStatement(DAOImpl.SELECT_QUERY)).andReturn(mockPrepareStatement);
        	mockPrepareStatement.setString(1, id);
        	expectLastCall().times(1);
        	expect(mockPrepareStatement.getWarnings()).andReturn(null);
        	expect(mockPrepareStatement.executeQuery()).andReturn(mockResultSet);
        	expect(mockResultSetExtractor.extractData(mockResultSet)).andReturn(map);
    		replay(mockResultSet, mockPrepareStatement, mockConnection, mockDataSource, mockResultSetExtractor);
    
    		Map<String, Map<String, BigInteger>> result = daoImpl.getWorkingFields(id);
    		assertEquals(map, result);
    		verify(mockResultSet, mockPrepareStatement, mockConnection, mockDataSource, mockResultSetExtractor);
    
    	}
    
    }

    Comment


    • #3
      And why should it work... You are creating 2 independend object (your dao and jdbctemplate) and expect them automatically to work together, now if you would inject your jdbctemplate then it would work.

      Although you are extending JdbcDaoSupport and are creating a SimpleJdbcTemplate.

      Comment


      • #4
        srikanthradix:
        I did actually have expect( mockDataSource.getConnection() ) before.

        It seems that using the deprecated classextension EasyMock.createMock won't let me use my mocked objects as I want to, since it simply ignores any expect() and replay(). I changed it to use the recommended method of mocking a class, which leads me to a whole new problem. I'll post it if I can't get that to work either. But thanks for trying!

        Martin:
        Isn't that the point of making mock objects? I was hoping I could get the mock SimpleJdbcTemplate to work so I could bypass the larger slew of expect/replay calls for Connection, Statement, etc I'd need without it.

        Comment


        • #5
          Sigh, again reading seems to be hard.

          You create a dao, you create a SImpleJdbcTemplate but you aren't injecting the JdbcTemplate into the dao hence it knows NOTHING about your mock.

          Comment


          • #6
            Marten:
            I'll post a condensed version of the DAO I'm testing.

            I should also note, in case you are wondering, that due to the structure of the project, I cannot simply pass a SimpleJdbcTemplate (or SimpleJdbcOperations, which would have been so much easier for testing) in as a parameter from an outside class. The queries must be built and inserted from this class.

            Code:
            import java.util.ArrayList;
            import java.util.List;
            import javax.sql.DataSource;
            
            import org.springframework.dao.DataAccessException;
            import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
            
            public class MyDAO implements DAOInterface {
                
                /** Default query for inserting record information. */
                private static final String INFO_QUERY = "INSERT INTO recordtable" + 
                    "( record_id, report_id, date ) " +
                    "VALUES( '?', '?', date('?') );";
                
                /** Default query for inserting source information. */
                private static final String SOURCE_QUERY = "INSERT INTO sourcetable ( record_id, contact_name )" +
                    "VALUES ( '?', '?' );";
                
                /** A reference to the data source to be queried. */
                private DataSource dataSource;
                
                /** Reference to a SimpleJdbcTemplate for handling the batch query. */
                private SimpleJdbcTemplate simpleJdbcTemplate;
                
                /** List of parameters for info insert query. */
                private List<Object[]> infoParameters;
                
                /** List of parameters for source insert query. */
                private List<Object[]> sourceParameters;
            
                
                public PostgresEventDAO(){
                }
                
                public void setDataSource( DataSource dataSource ) {
            
                    if( dataSource == null ){
                        throw new IllegalArgumentException( NULL_DATASOURCE );
                    }
                    this.dataSource = dataSource;
                }
                
                public boolean insertQueries( int count ) {
            
                    if( dataSource == null ){
                        throw new IllegalArgumentException( NULL_DATASOURCE );
                    }
                    else{
                        simpleJdbcTemplate = new SimpleJdbcTemplate( dataSource );
                    }
                    
                    infoParameters = new ArrayList<Object[]>();
                    sourceParameters = new ArrayList<Object[]>();
                    
                    boolean success = true;
            
                    for( int i = 0; i < count; i++ ){
            
                        fillInfoParameters();
                        fillSourceParameters();
                    }
                    
                    try{
                        addToBatch();
                    }
                    catch( IllegalArgumentException iaException ){
                        success = false;
                    }
                    
                    .
                    .
                    .
                }
                
                private void addToBatch(){
                    
                    if( simpleJdbcTemplate == null ){
                        throw new IllegalArgumentException( "JdbcTemplate is null, cannot add to batch statement." );
                    }
            
                    try{
                        simpleJdbcTemplate.batchUpdate( INFO_QUERY, infoParameters );
                        simpleJdbcTemplate.batchUpdate( SOURCE_QUERY, sourceParameters );
                    }
                    catch( DataAccessException daException ){
                        throw new IllegalArgumentException();
                    }
                }
            }

            Comment


            • #7
              (Simple)JdbcTemplate is thread safe so you should create it once (either inject it or create it when setting the datasource) you shouldn't create a new one for each query this is creating massive overhead.

              (Simple)JdbcTemplate is a heavy object to generate.

              I also suggest extending (Simple)JdbcDaoSupport that way you can inject either the datasource or the SimpleJdbcTemplate, the latter is what you need.

              Also for some reason your class seems to go against the recommended design principles and is therefor probably complexer then needed.

              Comment


              • #8
                Actually the insertQueries method is only called once, so as far as I can tell the (Simple)JdbcTemplate will only be created once.

                I tried extending SimpleJdbcDaoSupport as you suggested. However, that raised the problem of setting the dataSource so I could access it for other things.

                I decided to go back to using all the expect() calls done step-by-step (datasource.getconnection, connection.preparestatement... etc). It's maybe not as clean as mocking SimpleJdbcTemplate but it doesn't require reworking the DAO class code.

                Thanks for all the help!

                Comment


                • #9
                  Actually the insertQueries method is only called once, so as far as I can tell the (Simple)JdbcTemplate will only be created once.
                  It is created EACH TIME you call that method, creating significant overhead.

                  I tried extending SimpleJdbcDaoSupport as you suggested. However, that raised the problem of setting the dataSource so I could access it for other things.
                  Other things such as? If you need direct access use a ConnectionCallback that will give you a spring managed connection.

                  Comment

                  Working...
                  X