Junit 如何模拟 namedParameterJdbcTemplate.query(" ", parameters,(ResultSet rs))
Junit How to mock namedParameterJdbcTemplate.query(" ", parameters,(ResultSet rs))
我正在为存储库 类 编写测试用例,但我无法覆盖存储库 类 中的某些行。我需要达到 85% 的代码覆盖率,在我的情况下这是强制性的,请给我一些建议
我的实际方法
public Map<String, String> getProductFamily(List<String> itmNms) {
Map<String, String> productFamilyMap=new HashMap<String, String>();
try {
NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate);
String sql = "some query";
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
namedParameters.addValue("itmNms", itmNms);
productFamilyMap = namedParameterJdbcTemplate.query(sql, namedParameters, (ResultSet rs) -> {
Map<String, String> productFamily = new HashMap<>();
while (rs.next()) {
productFamily.put(rs.getString("ITEMNAME"), rs.getString("PRODUCTFAMILY"));
}
return productFamily;
});
}catch (Exception e) {
LOGGER.error("Exception in OracleRespository.getProductFamily : {}", e);
}
return productFamilyMap;
}
上述方法的测试用例
@Test
public void getProductFamily() {
List<String> itmNms = new ArrayList<String>();
itmNms.add("A-SPK-NAMED-USER");
oracleRepo.getProductFamily(itmNms);
Map<String, String> mp = new HashMap<String, String>();
Assert.assertNull(mp);
}
通过编写上面的测试用例,我能够覆盖代码覆盖范围,直到第 6 行以下,由于以下语句我无法覆盖
productFamilyMap = namedParameterJdbcTemplate.query(sql, namedParameters, (ResultSet rs) ->{}
有人可以建议我怎样才能使上述方法的代码覆盖率达到 100%。
在这种情况下,您需要 "manually invoke" lambda 中的代码。这可以通过 Mockito 框架的 Mockito.doAnswer(...)
功能来执行。示例(适用于 Mockito 2+):
Mockito.doAnswer(invocationOnMock -> {
ResultSet resultSet = Mockito.mock(ResultSet.class);
Mockito.when(resultSet.next()).thenReturn(true).thenReturn(false);
Mockito.when(resultSet.getString("ITEMNAME")).thenReturn(...);
Mockito.when(resultSet.getString("PRODUCTFAMILY")).thenReturn(...);
ResultSetExtractor<Map<String, String>> resultSetExtractor =
invocationOnMock.getArgument(2);
return resultSetExtractor.extractData(resultSet);
}).when(namedParameterJdbcTemplate).query(
Mockito.anyString(),
Mockito.any(MapSqlParameterSource.class),
Mockito.any(ResultSetExtractor.class)
);
然后您可以验证 productFamilyMap
填充的键值对。
如果您仍然遇到问题,可以分享您的代码(例如通过 Github),我会尽力帮助您。
编辑: 最初,我没有注意到 NamedParameterJdbcTemplate
是用 new
手动创建的,而且很难模拟它。在这种情况下,最好稍微重构一下您的生产代码——您可以将 NamedParameterJdbcTemplate
对象创建为 bean(就像您可能对原始 JdbcTemplate
所做的那样),然后将其注入到您的 class (和 ofc 删除您使用 new
创建它的行)。那么事情就变得微不足道了。
@Component
public class OracleRepository {
private static final Logger LOGGER = LoggerFactory.getLogger(OracleRepository.class);
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate; //created as bean in configuration class
public Map<String, String> getProductFamily(List<String> itmNms) {
Map<String, String> productFamilyMap=new HashMap<String, String>();
try {
String sql = "some query";
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
namedParameters.addValue("itmNms", itmNms);
productFamilyMap = namedParameterJdbcTemplate.query(sql, namedParameters, (ResultSet rs) -> {
Map<String, String> productFamily = new HashMap<>();
while (rs.next()) {
productFamily.put(rs.getString("ITEMNAME"), rs.getString("PRODUCTFAMILY"));
}
return productFamily;
});
}catch (Exception e) {
LOGGER.error("Exception in OracleRespository.getProductFamily : {}", e);
}
return productFamilyMap;
}
}
测试class保持不变:
@RunWith(MockitoJUnitRunner.class)
public class OracleRepositoryTest {
@InjectMocks
private OracleRepository oracleRepo;
@Mock
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Test
public void getProductFamily() {
List<String> itmNms = new ArrayList<>();
itmNms.add("A-SPK-NAMED-USER");
Mockito.doAnswer(invocationOnMock ->{
ResultSet resultSet = Mockito.mock(ResultSet.class);
Mockito.when(resultSet.next()).thenReturn(true).thenReturn(false);
Mockito.when(resultSet.getString("ITEMNAME")).thenReturn("A-SPK-NAMED-USER");
Mockito.when(resultSet.getString("PRODUCTFAMILY")).thenReturn("SPKCLD");
ResultSetExtractor<Map<String, String>> resultSetExtractor =
invocationOnMock.getArgument(2);
return resultSetExtractor.extractData(resultSet);
}).when(namedParameterJdbcTemplate).query(
Mockito.anyString(),
Mockito.any(MapSqlParameterSource.class),
Mockito.any(ResultSetExtractor.class)
);
Map<String, String> productFamilyMap = oracleRepo.getProductFamily(itmNms);
Assert.assertEquals("SPKCLD", productFamilyMap.get("A-SPK-NAMED-USER"));
}
}
以上答案准确且有效。我也在我的项目中尝试过。
让我试着解释一下这是如何工作的。
我们说的是当模拟的 JDBCTemplate query() 方法被调用时,我们想要调用我们自己的 lambda 表达式并完成一些模拟,就像我们首先创建一个模拟的结果集,并模拟它的一些 getString 方法。接下来我们捕获模拟调用的第三个参数,即结果集提取器。现在从这里我们简单地 return 这个提取器提取数据方法和我们现在将被调用的模拟结果集。
所以本质上我们是用我们模拟的结果集调用原始的提取数据方法。
Mockito.doAnswer(invocationOnMock -> {
ResultSet resultSet = Mockito.mock(ResultSet.class);
Mockito.when(resultSet.next()).thenReturn(true).thenReturn(false);
Mockito.when(resultSet.getString("ITEMNAME")).thenReturn(...);
Mockito.when(resultSet.getString("PRODUCTFAMILY")).thenReturn(...);
ResultSetExtractor<Map<String, String>> resultSetExtractor =
invocationOnMock.getArgument(2);
return resultSetExtractor.extractData(resultSet);
}).when(namedParameterJdbcTemplate).query(
Mockito.anyString(),
Mockito.any(MapSqlParameterSource.class),
Mockito.any(ResultSetExtractor.class)
);
我正在为存储库 类 编写测试用例,但我无法覆盖存储库 类 中的某些行。我需要达到 85% 的代码覆盖率,在我的情况下这是强制性的,请给我一些建议
我的实际方法
public Map<String, String> getProductFamily(List<String> itmNms) {
Map<String, String> productFamilyMap=new HashMap<String, String>();
try {
NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate);
String sql = "some query";
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
namedParameters.addValue("itmNms", itmNms);
productFamilyMap = namedParameterJdbcTemplate.query(sql, namedParameters, (ResultSet rs) -> {
Map<String, String> productFamily = new HashMap<>();
while (rs.next()) {
productFamily.put(rs.getString("ITEMNAME"), rs.getString("PRODUCTFAMILY"));
}
return productFamily;
});
}catch (Exception e) {
LOGGER.error("Exception in OracleRespository.getProductFamily : {}", e);
}
return productFamilyMap;
}
上述方法的测试用例
@Test
public void getProductFamily() {
List<String> itmNms = new ArrayList<String>();
itmNms.add("A-SPK-NAMED-USER");
oracleRepo.getProductFamily(itmNms);
Map<String, String> mp = new HashMap<String, String>();
Assert.assertNull(mp);
}
通过编写上面的测试用例,我能够覆盖代码覆盖范围,直到第 6 行以下,由于以下语句我无法覆盖
productFamilyMap = namedParameterJdbcTemplate.query(sql, namedParameters, (ResultSet rs) ->{}
有人可以建议我怎样才能使上述方法的代码覆盖率达到 100%。
在这种情况下,您需要 "manually invoke" lambda 中的代码。这可以通过 Mockito 框架的 Mockito.doAnswer(...)
功能来执行。示例(适用于 Mockito 2+):
Mockito.doAnswer(invocationOnMock -> {
ResultSet resultSet = Mockito.mock(ResultSet.class);
Mockito.when(resultSet.next()).thenReturn(true).thenReturn(false);
Mockito.when(resultSet.getString("ITEMNAME")).thenReturn(...);
Mockito.when(resultSet.getString("PRODUCTFAMILY")).thenReturn(...);
ResultSetExtractor<Map<String, String>> resultSetExtractor =
invocationOnMock.getArgument(2);
return resultSetExtractor.extractData(resultSet);
}).when(namedParameterJdbcTemplate).query(
Mockito.anyString(),
Mockito.any(MapSqlParameterSource.class),
Mockito.any(ResultSetExtractor.class)
);
然后您可以验证 productFamilyMap
填充的键值对。
如果您仍然遇到问题,可以分享您的代码(例如通过 Github),我会尽力帮助您。
编辑: 最初,我没有注意到 NamedParameterJdbcTemplate
是用 new
手动创建的,而且很难模拟它。在这种情况下,最好稍微重构一下您的生产代码——您可以将 NamedParameterJdbcTemplate
对象创建为 bean(就像您可能对原始 JdbcTemplate
所做的那样),然后将其注入到您的 class (和 ofc 删除您使用 new
创建它的行)。那么事情就变得微不足道了。
@Component
public class OracleRepository {
private static final Logger LOGGER = LoggerFactory.getLogger(OracleRepository.class);
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate; //created as bean in configuration class
public Map<String, String> getProductFamily(List<String> itmNms) {
Map<String, String> productFamilyMap=new HashMap<String, String>();
try {
String sql = "some query";
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
namedParameters.addValue("itmNms", itmNms);
productFamilyMap = namedParameterJdbcTemplate.query(sql, namedParameters, (ResultSet rs) -> {
Map<String, String> productFamily = new HashMap<>();
while (rs.next()) {
productFamily.put(rs.getString("ITEMNAME"), rs.getString("PRODUCTFAMILY"));
}
return productFamily;
});
}catch (Exception e) {
LOGGER.error("Exception in OracleRespository.getProductFamily : {}", e);
}
return productFamilyMap;
}
}
测试class保持不变:
@RunWith(MockitoJUnitRunner.class)
public class OracleRepositoryTest {
@InjectMocks
private OracleRepository oracleRepo;
@Mock
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Test
public void getProductFamily() {
List<String> itmNms = new ArrayList<>();
itmNms.add("A-SPK-NAMED-USER");
Mockito.doAnswer(invocationOnMock ->{
ResultSet resultSet = Mockito.mock(ResultSet.class);
Mockito.when(resultSet.next()).thenReturn(true).thenReturn(false);
Mockito.when(resultSet.getString("ITEMNAME")).thenReturn("A-SPK-NAMED-USER");
Mockito.when(resultSet.getString("PRODUCTFAMILY")).thenReturn("SPKCLD");
ResultSetExtractor<Map<String, String>> resultSetExtractor =
invocationOnMock.getArgument(2);
return resultSetExtractor.extractData(resultSet);
}).when(namedParameterJdbcTemplate).query(
Mockito.anyString(),
Mockito.any(MapSqlParameterSource.class),
Mockito.any(ResultSetExtractor.class)
);
Map<String, String> productFamilyMap = oracleRepo.getProductFamily(itmNms);
Assert.assertEquals("SPKCLD", productFamilyMap.get("A-SPK-NAMED-USER"));
}
}
以上答案准确且有效。我也在我的项目中尝试过。 让我试着解释一下这是如何工作的。
我们说的是当模拟的 JDBCTemplate query() 方法被调用时,我们想要调用我们自己的 lambda 表达式并完成一些模拟,就像我们首先创建一个模拟的结果集,并模拟它的一些 getString 方法。接下来我们捕获模拟调用的第三个参数,即结果集提取器。现在从这里我们简单地 return 这个提取器提取数据方法和我们现在将被调用的模拟结果集。 所以本质上我们是用我们模拟的结果集调用原始的提取数据方法。
Mockito.doAnswer(invocationOnMock -> {
ResultSet resultSet = Mockito.mock(ResultSet.class);
Mockito.when(resultSet.next()).thenReturn(true).thenReturn(false);
Mockito.when(resultSet.getString("ITEMNAME")).thenReturn(...);
Mockito.when(resultSet.getString("PRODUCTFAMILY")).thenReturn(...);
ResultSetExtractor<Map<String, String>> resultSetExtractor =
invocationOnMock.getArgument(2);
return resultSetExtractor.extractData(resultSet);
}).when(namedParameterJdbcTemplate).query(
Mockito.anyString(),
Mockito.any(MapSqlParameterSource.class),
Mockito.any(ResultSetExtractor.class)
);