如何Mock并单元测试EntityManager与Query?空指针问题排查
问题分析与解决方案
咱们一步步拆解你遇到的问题,既有测试代码的配置失误,也有业务代码本身的逻辑bug:
一、测试代码的核心错误
1. 重复Mock导致EntityManager实例不匹配
你已经用@Mock注解声明了EntityManager和Query,但在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实例,后续的手动赋值会把这个有效实例替换掉。更关键的是,你测试方法里用的em(when(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




