2012-07-23 116 views
0

我需要你的幫助來解決一個問題。我在我的DAO類這樣的方法,節省了角色:使用Mockito嘲笑Spring的jdbc KeyHolder

@Repository(value="jdbcRoleDAO") 
@Transactional(propagation=Propagation.SUPPORTS, readOnly=true, rollbackFor=Exception.class) 
public class JdbcRoleDAO implements RoleDAO { 

    @Autowired 
    private JdbcTemplate jdbcTemplate; 

    @Autowired 
    private DBLogger<Role> dbLogger; 

    /** 
    * @throws DublicateEntryException if object with specified unique for application field already 
    *    exists in DB. 
    */ 
    @CacheEvict(value = "roles", allEntries = true) 
    @Transactional(propagation=Propagation.REQUIRED, readOnly=false, rollbackFor=Exception.class) 
    @Override 
    public Role save(final Role role) { 

     if (this.getRoleByName(role.getName()) != null) { 
      throw new DublicateEntryException("Record with specified role name already exists in application."); 
     } 

     KeyHolder keyHolder = new GeneratedKeyHolder(); 
     jdbcTemplate.update(new PreparedStatementCreator() { 

      @Override 
      public PreparedStatement createPreparedStatement(Connection connection) 
        throws SQLException { 

       PreparedStatement ps = connection.prepareStatement(SQL_INSERT_ROLE, new String[]{"ID"}); 

       ps.setString(1, role.getName()); 
       ps.setString(2, role.getDesription()); 
       ps.setObject(3, (role.getParentRole()!=null)?role.getParentRole().getId():null); 
       ps.setString(4, role.getPrivilege().toString()); 

       return ps; 
      } 
     }, keyHolder); 

     Long key = (Long) keyHolder.getKey(); 
     if (key != null) { 
      role.setId(key); 
     } else { 
      role.setId(-1); 
     } 

     // Add information about operation to log table 
     dbLogger.logBasicOperation(role, OperationName.CREATE); 

     return role; 
    } 
... 
} 

我想要寫單元測試使用這個的Mockito方法。現在它看起來像:

@RunWith(MockitoJUnitRunner.class) 
public class JdbcRoleDAOTest { 

    private static final long TEST_ROLE_ID = 1L; 
    private static final int TEST_ROLE_VERSION = 7; 

    @Mock 
    private DBLogger<Role> dbLogger; 

    @Mock 
    private JdbcTemplate jdbcTemplate; 

    @InjectMocks 
    private JdbcRoleDAO roleDAO; 

    /** 
    * test role that is used in all methods. 
    */ 
    private Role role; 

    @Before 
    public void setUp(){ 
     // Create a test role object 
     role = new Role(); 
     role.setId(TEST_ROLE_ID); 
     role.setName("TEST"); 
     role.setDesription("Test role"); 
     role.setPrivilege(Privilege.DEFAULT); 
     role.setVersion(TEST_ROLE_VERSION); 

     // Return correct version of the object 
     Mockito.when(jdbcTemplate.queryForInt(JdbcRoleDAO.SQL_GET_VERSION, TEST_ROLE_ID)) 
      .thenReturn(TEST_ROLE_VERSION); 
    } 

    @Test 
    public void testSave() { 
     Assert.assertNotNull(role); 

     roleDAO.save(role); 

     InOrder inOrder = Mockito.inOrder(jdbcTemplate, dbLogger); 

     // Verify execution of the conditions. 
     inOrder.verify(jdbcTemplate, Mockito.times(1)).update(Mockito.any(PreparedStatementCreator.class), 
       Mockito.any(KeyHolder.class)); 

     inOrder.verify(dbLogger, Mockito.times(1)).logBasicOperation(role, OperationName.CREATE); 

     /* 
     * Expected -1 because I can't mock KeyHolder and should be returned -1 value (because 
     * KeyHolder will return null instead of key) 
     */ 
     Assert.assertEquals("Generated id is wrong!", -1, role.getId()); 
    } 
… 
} 

的問題是,我想以某種方式嘲笑持有者,但我不知道如何更好地做到這一點。正如你所看到的,現在KeyHolder實例是在保存方法中創建的,這是一個主要難點。我想過使用Spring的範圍「原型」注入KeyHolder,但據我所知,當少數用戶同時嘗試保存新的角色時,這可能會導致問題。此外,我試圖做這樣的事情:

Mockito.when(jdbcTemplate.update(Mockito.any(PreparedStatementCreator.class), 
       Mockito.any(KeyHolder.class))) 
       .thenAnswer(new Answer<Void>() { 

        @Override 
        public Void answer(InvocationOnMock invocation) 
          throws Throwable { 

         Object[] arguments = invocation.getArguments(); 
         for (int i=0; i<arguments.length; i++) { 
          if (arguments[i] instanceof KeyHolder) { 
           KeyHolder keyHolder = (KeyHolder) arguments[i]; 

           KeyHolder spyKeyHolder = Mockito.spy(keyHolder); 
           Mockito.when(spyKeyHolder.getKey()).thenReturn(TEST_GENERATED_ID); 
           Mockito.doReturn(TEST_GENERATED_ID).when(spyKeyHolder).getKey(); 
          } 
         } 

         return null; 
        } 
       }); 

但這不起作用。 可能有人可以給我一個建議如何有可能嘲笑(或間諜)KeyHolder不會從保存方法外部移動它? Mockito中有一些功能可以使這成爲可能嗎?或者這是不可能的?

預先感謝您

回答

4

您可以創建以下接口:

public interface KeyHolderFactory { 
    KeyHolder newKeyHolder(); 
} 

然後在您的JdbcRoleDAO中添加一個KeyHolderFactory字段,通過生產中的依賴注入來填充,並在測試時手動進行模擬。該生產實施很簡單,你可以用它(單身)的單一實例:

public class GeneratedKeyHolderFactory implements KeyHolderFactory { 
    public KeyHolder newKeyHolder() { 
     return new GeneratedKeyHolder(); 
    } 
} 

裏面的方法,你將有

... 
KeyHolder keyHolder = factory.newKeyHolder(); 
... 
+0

是的,我認爲這將工作良好。謝謝 – dimas 2012-07-23 18:00:25

0

如果你可以切換到PowerMock你也能嘲笑構造函數,然後你可以嘲笑GeneratedKeyHolder構造函數返回一個模擬持有者