Mockito与Spring Boot环境下JUnit测试用例问题排查与优化
业务类代码
public class EmployeeCrudOperation { public void deleteEmployee(String id) { StringBuilder deleteQuery = new StringBuilder("DELETE FROM EMPLOYEE WHERE ID = ?"); PreparedStatement preparedStatement = null; Connection connection = null; try{ //connection = DBConnection.getInstance().getConnection(); //it contains JNDI connection code called under static block connection = getConnection(); if(connection == null) { logger.logMessage("some text"); return; } preparedStatement = connection.prepareStatement(deleteQuery.toString()); preparedStatement.setString(1, id); int n = preparedStatement.executeUpdate(); if(n == 0) { logger.logMessage("some message"); } if(preparedStatement != null) { preparedStatement.close(); } } catch(SQLException e) { logger.logMessage("some message"); } catch(Exception e) { logger.logMessage("some message"); } finally { try { if(preparedStatement != null) { preparedStatement.close(); } } catch(Exception e) { } try { if(connection != null) { connection.close(); } } catch(Exception e) {} } } public Connection getConnection() { return DBConnection.getInstance().getConnection(); //it contains JNDI connection code called under static block } }
测试用例代码
public class TestEmployeeCrudOperation { @Mock Connection connection; @Mock PreparedStatement preparedStatement; @Test public void testDeleteEmployee() { EmployeeCrudOperation employeeCrudOperation = new EmployeeCrudOperation(); EmployeeCrudOperation spyEmployeeCrudOperation = Mockito.spy(employeeCrudOperation); try { when(spyEmployeeCrudOperation.getConnection()).thenReturn(connection); when(connection.preparedStatement(Mockito.any())).thenReturn(preparedStatement); when(preparedStatement.executeUpdate()).thenReturn(1); spyEmployeeCrudOperation.deleteEmplotee("1"); verify(spyEmployeeCrudOperation, times(1)).deleteEmplotee("1"); } catch(Exception e) { e.printStackTrace(); } } }
问题解答
1. 当前测试用例是否正确?
不正确,存在以下问题:
- 方法名拼写错误:测试中调用的
deleteEmplotee应为业务类中的deleteEmployee,拼写错误会导致找不到目标方法,测试逻辑直接失效。 - 缺少Mock初始化:
@Mock注解需要配合初始化操作才能生效,要么在测试类上添加@ExtendWith(MockitoExtension.class),要么在测试方法开头调用MockitoAnnotations.openMocks(this),否则connection和preparedStatement会是null。 - Verify逻辑无效:测试核心是验证业务逻辑对依赖的调用(如数据库连接、SQL执行),而非验证
deleteEmployee方法的调用次数——这属于无意义验证,因为测试代码本身就主动调用了一次。 - 未覆盖分支场景:当前测试仅覆盖了正常执行的分支,完全忽略了连接为空、SQL执行无影响行数、异常抛出等关键场景。
2. 如何提升代码覆盖率?
要覆盖业务类所有代码分支,需针对每个场景编写测试:
- 覆盖connection为null的分支:设置
when(spyEmployeeCrudOperation.getConnection()).thenReturn(null),验证logger.logMessage是否被调用,且方法提前返回。 - 覆盖executeUpdate返回0的分支:设置
when(preparedStatement.executeUpdate()).thenReturn(0),验证logger.logMessage是否输出对应提示。 - 覆盖SQLException分支:让
connection.prepareStatement()或preparedStatement.executeUpdate()抛出SQLException,验证异常被捕获且日志输出。 - 覆盖通用Exception分支:让
getConnection()或其他步骤抛出非SQL的Exception,验证异常处理逻辑。 - 覆盖finally块资源关闭逻辑:包括
preparedStatement/connection不为空时的关闭操作,甚至模拟关闭时抛出异常的场景(确保finally块不会因异常中断)。 - 修正测试基础错误:先修复拼写、Mock初始化等问题,确保测试能正常触发真实业务逻辑。
3. 调试时无法进入实际业务类的解决方法
- 修正方法名拼写:之前的
deleteEmplotee拼写错误会导致测试调用不存在的方法,自然无法进入业务类,需修正为正确的deleteEmployee。 - 检查Mock初始化状态:如果
@Mock未初始化,依赖对象会是null,触发业务类异常分支,跳过正常逻辑;确保Mock对象正确初始化。 - 调整Mockito Spy行为:Mockito Spy默认调用真实方法,但如果对
getConnection()等方法做了Stub,确保不会完全阻断真实逻辑执行(无需Stub的方法不要强行Stub)。 - 检查IDE调试配置:确认IDE未开启“跳过指定类的调试”,避免业务类被排除在断点之外;同时确保断点打在业务类的正确位置。
- 排查类加载问题:确保业务类和测试类使用同一类加载器,避免因类加载不一致导致调试无法关联业务类代码。
内容的提问来源于stack exchange,提问作者Gopal Lal
相关产品推荐
相关产品推荐

