为何Mock的Repository仍尝试连接数据库?——自定义RESTful风格API服务层单元测试问题排查
问题原因分析
我来帮你拆解下这个问题的核心:
你遇到的数据库连接尝试,根本原因是HibernateController里的静态成员初始化逻辑,和Mockito的Mock机制不兼容:
HibernateController中的sessionFactory和session是静态变量,JVM在加载这个类的时候就会执行HibernateUtil.getSessionFactory()和sessionFactory.openSession()——这个过程发生在你创建Mock版ArtikelRepository之前。- Mockito的Mock是基于动态代理实现的,它只能Mock实例方法、拦截实例级别的调用,但完全管不了类加载阶段的静态初始化逻辑。所以哪怕你Mock了
ArtikelRepository,父类HibernateController的静态代码已经提前执行,触发了真实的数据库连接流程。
解决建议与替代方案
根据你的代码情况,推荐以下几种解决方案,按优先级排序:
方案一:重构HibernateController,改用依赖注入(最推荐)
这是从根源上解决问题的方式,也更符合面向对象设计原则:
把静态成员改成实例成员,通过构造方法注入SessionFactory,避免类加载时的强制初始化:
// 重构后的HibernateController public class HibernateController { private final SessionFactory sessionFactory; private Session session; // 通过构造方法注入SessionFactory public HibernateController(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; this.session = sessionFactory.openSession(); } public void deleteById(Object id, Class deleteClass) { session.beginTransaction(); session.delete(session.find(deleteClass, id)); session.getTransaction().commit(); } // ...其他方法 } // 重构后的ArtikelRepository public class ArtikelRepository extends HibernateController implements Serializable { public ArtikelRepository(SessionFactory sessionFactory) { super(sessionFactory); } }
测试时,你可以直接MockSessionFactory传给Repository,或者继续MockArtikelRepository的方法——此时父类的初始化不会触发真实数据库连接,因为依赖的是Mock的SessionFactory。
方案二:用PowerMock拦截静态初始化(无需重构代码)
如果暂时无法重构现有代码,可以用PowerMock来拦截静态方法和类的静态初始化流程:
- 首先引入PowerMock依赖(根据你的JUnit版本选择):
<!-- JUnit 5 + Mockito 2/3 --> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit5</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> - 修改测试类,Mock静态方法和相关依赖:
@ExtendWith({MockitoExtension.class, PowerMockExtension.class}) @PrepareForTest({HibernateUtil.class, HibernateController.class}) class ArtikelServiceUnittest { @Mock ArtikelRepository artikelRepository; @Mock SessionFactory mockSessionFactory; @Mock Session mockSession; @Mock Transaction mockTransaction; ArtikelService cut; ArtikelEntity givenArtikelEntity = new ArtikelEntity(1, "Testartikel", "Test", 1, 1.00); @BeforeEach void setUp() { // 拦截HibernateUtil的静态方法,返回Mock的SessionFactory PowerMockito.mockStatic(HibernateUtil.class); when(HibernateUtil.getSessionFactory()).thenReturn(mockSessionFactory); // Mock SessionFactory的openSession方法 when(mockSessionFactory.openSession()).thenReturn(mockSession); // Mock Session的事务方法,避免空指针 when(mockSession.beginTransaction()).thenReturn(mockTransaction); cut = new ArtikelService(artikelRepository); } @Test void shouldReturnAListWithTheGivenEntityAsDto() { // given List givenList = new ArrayList() {{ add(givenArtikelEntity.toArtikelDto()); }}; when(artikelRepository.getAll("ArtikelEntity")).thenReturn(givenList); // when List result = cut.getAllArtikel(); // then assertEquals(givenList, result); } }
PowerMock会拦截HibernateController的静态初始化流程,让它使用你Mock的SessionFactory,从而避免真实的数据库连接。
方案三:给HibernateUtil添加测试开关(侵入性较小的临时方案)
在HibernateUtil中增加一个测试专用的方法,允许手动设置SessionFactory:
public class HibernateUtil { private static SessionFactory sessionFactory; public static SessionFactory getSessionFactory() { if (sessionFactory == null) { // 原有的真实初始化逻辑 } return sessionFactory; } // 测试专用:手动设置SessionFactory public static void setSessionFactoryForTest(SessionFactory factory) { sessionFactory = factory; } }
然后在测试类的@BeforeEach中提前设置Mock的SessionFactory:
@BeforeEach void setUp() { SessionFactory mockSessionFactory = Mockito.mock(SessionFactory.class); HibernateUtil.setSessionFactoryForTest(mockSessionFactory); // 后续Mock Session等逻辑... artikelRepository = Mockito.mock(ArtikelRepository.class); cut = new ArtikelService(artikelRepository); }
这样HibernateController初始化时会使用你设置的MockSessionFactory,不会触发真实连接。
内容的提问来源于stack exchange,提问作者MFrikken
相关产品推荐
相关产品推荐

