You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何Mock并单元测试EntityManager与Query?空指针问题排查

问题分析与解决方案

咱们一步步拆解你遇到的问题,既有测试代码的配置失误,也有业务代码本身的逻辑bug:

一、测试代码的核心错误

1. 重复Mock导致EntityManager实例不匹配

你已经用@Mock注解声明了EntityManagerQuery,但在setUp方法里又手动重新Mock了一次:

@Mock EntityManager entityManager; 
@Mock Query query; 
@Before 
public void setUp() { 
    MockitoAnnotations.initMocks(this); 
    // 这两行完全多余,还会覆盖@Mock创建的合法mock对象
    entityManager = Mockito.mock(em.class); // 这里的em应该是EntityManager.class吧?变量名写错了
    query = Mockito.mock(Query.class); 
}

MockitoAnnotations.initMocks(this)已经会为标注@Mock的变量创建mock实例,后续的手动赋值会把这个有效实例替换掉。更关键的是,你测试方法里用的emwhen(em.createQuery(...)))如果和测试类里的entityManager变量名不一致,就会导致调用的是未初始化的对象,自然返回null。

修正方式:删掉setUp里的手动Mock代码,改用@RunWith(MockitoJUnitRunner.class)简化初始化,同时用@InjectMocks把mock注入到service中:

@RunWith(MockitoJUnitRunner.class)
public class YourServiceTest {
    @Mock 
    private EntityManager entityManager; 
    @Mock 
    private Query query; 

    @InjectMocks
    private YourServiceImpl serviceImpl; // 自动把mock注入到service的成员变量里

    // 测试方法...
}

2. Mock匹配的JPQL和业务代码实际调用的不一致(业务代码bug导致)

先看你的业务代码逻辑:

StringBuilder jpqlQuery=new StringBuilder("select Distinct s.modelCode from Model s where 1=1"); 
Query query = entityManager.createQuery(jpqlQuery.toString()); // 先创建Query,用的是初始JPQL
if(myModel != null ) { 
    jpqlQuery.append(" and s.name = :name"); // 后拼接条件,Query已经创建完成!
} 
if(myModel != null){ 
    query.setParameter("name", myModel.getFirstName()); // 给旧Query设参数,但旧Query的JPQL根本没有这个参数!
}

这是业务代码的致命bug:你先基于初始JPQL创建了Query,之后才拼接条件,导致实际执行的Query不包含s.name = :name,反而调用setParameter会触发参数不匹配异常(如果不是空指针的话)。

必须先修正这个逻辑:

StringBuilder jpqlQuery=new StringBuilder("select Distinct s.modelCode from Model s where 1=1"); 
if(myModel != null ) { 
    jpqlQuery.append(" and s.name = :name"); 
} 
// 所有条件拼接完成后,再创建Query
Query query = entityManager.createQuery(jpqlQuery.toString()); 
if(myModel != null){ 
    query.setParameter("name", myModel.getFirstName()); 
}

3. 测试方法的Mock匹配逻辑需要调整

修正业务代码后,测试里的JPQL需要和业务拼接后的一致,或者用更灵活的匹配方式避免字符串差异:

@Test 
public void testGetModelNameDetails() { 
    List<String> results = new ArrayList<>(); 
    ModelDTO myModel = Mockito.mock(ModelDTO.class); 
    when(myModel.getFirstName()).thenReturn("Downtown"); 
    
    // 用anyString()匹配任意JPQL,避免动态拼接导致的匹配失败
    when(entityManager.createQuery(Mockito.anyString())).thenReturn(query); 
    when(query.getResultList()).thenReturn(results); 
    
    serviceImpl.getNameDetails(myModel); 
    
    // 验证参数设置是否符合预期
    Mockito.verify(query).setParameter("name", "Downtown");
}

二、总结你需要做的修改

  • 删掉测试类setUp里重复的手动Mock代码,用@RunWith@InjectMocks确保mock正确注入
  • 修正业务代码中先创建Query再拼接JPQL的逻辑错误
  • 测试方法里使用灵活的Mock匹配方式(比如anyString()),同时确保变量名和测试类里的一致
  • 测试最后添加验证逻辑,确认参数设置等行为符合预期

内容的提问来源于stack exchange,提问作者devCodePro

火山引擎 最新活动