我正在学习Mockito和单元测试。我想学习如何通过使用参数捕获器更好地进行单元测试。我正在使用jdbc来处理我的SQL语句。我有一个将用户插入我的数据库的方法。
public void insert(User user) {
String sql = "INSERT INTO user (id) VALUES ?";
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection connection) {
final PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, user.getId().trim());
return ps;
}
});
}
下面是我尝试用ArgumentCaptor编写的测试。
@Test
public void testInsert() {
User user = new User("testID");
ArgumentCaptor<PreparedStatementCreator> captor = ArgumentCaptor.forClass(PreparedStatementCreator.class);
insert(user);
verify(mockJdbc, times(1)).update(captor.capture());
PreparedStatementCreator actual = captor.getValue();
assertEquals(??, actual.createPreparedStatement(??));
}
关于断言语句的"??"中应该包含什么,或者这是否是使用参数捕获器的正确方法,任何建议或见解?
谢谢
编辑:
@Test
public void testInsert() throws SQLException {
ArgumentCaptor<PreparedStatementCreator> captor = ArgumentCaptor.forClass(PreparedStatementCreator.class);
PreparedStatement ps = mockConnection.prepareStatement("INSERT INTO user (id) VALUES ?";);
ps.setString(1, user.getId().trim());
insert(user);
verify(mockJdbcTemplate, times(1)).update(captor.capture());
PreparedStatementCreator actual = captor.getValue();
assertEquals(ps, actual.createPreparedStatement(mockConnection));
}
我喜欢你使用ArgumentCaptor
的方法。
您正确地使用了ArgumentCaptor
来捕获模拟的 JDBC 模板上update
的方法的参数;但是,您无法提取用于调用PreparedStatementCreator
的参数,因为这是对象不是模拟。
从概念上讲,您必须测试这部分代码的难度来自您无法控制PreparedStatementCreator
创建的事实。一种可能的解决方案是收回对创建这些对象的方式和时间的控制权;以便您在测试中模拟它们。
遵循标准的创建模式,您可以引入一个工厂,该工厂(单一)职责是创建PreparedStatementCreator
。
interface PreparedStatementCreatorFactory {
PreparedStatementCreator newPreparedStatementCreator(Connection connection, String sql, User user);
}
public final class DefaultPreparedStatementCreatorFactory {
@Override
public PreparedStatementCreator newPreparedStatementCreator(Connection connection, String sql, User user) {
final PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, user.getId().trim());
return ps;
}
}
然后,在你正在测试的类(包含 JDBC 模拟)中,你可以注入PreparedStatementCreatorFactory
的模拟。然后,您可以改为在工厂上捕获参数,而不是捕获 JDBC 模拟的参数;当然,还要指定模拟工厂返回的内容。
PreparedStatementCreatorFactory factory = Mockito.mock(PreparedStatementCreatorFactory.class);
PreparedStatementCreator creator = Mockito.mock(PreparedStatementCreator.class);
// Mock the creator at your convenience.
when(factory.newPreparedStatementCreator(any(Connection.class), any(String.class), any(User.class)).thenReturn(creator);
...
User user = new User("testID");
ArgumentCaptor<Connection> connectionCaptor = ArgumentCaptor.forClass(Connector.class);
ArgumentCaptor<String> sqlCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
insert(user);
verify(factory, times(1)).newPreparedStatementCreator(connectionCaptor.capture(), sqlCaptor.capture(), userCaptor.capture());
assertEquals(user, userCaptor.getValue());
这种方法的一个缺点是它增加了一个级别的间接性和相对复杂性;正如我们所看到的,主要优点是可以改进设计中关注点的分离,并精细地提高代码的可测试性。