2012-07-23 75 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构造函数返回一个模拟持有者