JUnit 5单元测试无法识别搜索方法正则致失败,如何解决?
MongoDB不区分大小写查询的单元测试失败问题
问题背景
开发了一个可基于名字(名、姓或两者)在MongoDB仓库中执行不区分大小写查询的程序,程序运行正常,但JUnit 5单元测试始终失败。
核心代码片段
服务层搜索方法
public List<Record1> searchRecord1ByName(String givenName, String lastName) { return searchRecord(givenName, lastName, (gn, ln) -> record1Repo.findByGivenNameAndLastName(gn, ln), (gn, ln) -> record1Repo.findByGivenName(gn), (gn, ln) -> record1Repo.findByLastName(ln)); } public <T> List<T> searchRecord(String givenName, String lastName, BiFunction<String, String, List<T>> searchBothNames, BiFunction<String, String, List<T>> searchGivenName, BiFunction<String, String, List<T>> searchLastName) { if ((givenName == null || givenName.isEmpty()) && (lastName == null || lastName.isEmpty())) { throw new InvalidSearchDataException("Both givenName and lastName cannot be null or empty."); } String givenNameRegex = givenName != null && !givenName.isEmpty() ? "^" + Pattern.quote(givenName) + "$" : null; String lastNameRegex = lastName != null && !lastName.isEmpty() ? "^" + Pattern.quote(lastName) + "$" : null; if (givenNameRegex != null && lastNameRegex != null) { return searchBothNames.apply(givenNameRegex, lastNameRegex); } else if (givenNameRegex != null) { return searchGivenName.apply(givenNameRegex, null); } else { return searchLastName.apply(null, lastNameRegex); } }
MongoDB仓库查询方法
@Query("{ 'givenName': { $regex: ?0, $options: 'i' } }") List<Record1> findByGivenName(String givenName);
JUnit 5测试代码
private final RecordSearchService recordSearchService = new RecordSearchService(mockRecord1Repo, mockRecord2Repo, mockRecord3Repo); @Test void searchRecord1ByGivenName() { Record1 record1 = Record1.builder() .givenName("TestGivenName") .lastName("TestLastName") .build(); List<Record1> expected = List.of(record1); when(mockRecord1Repo.findByGivenName(anyString())).thenAnswer(invocation -> { String givenNameArg = invocation.getArgument(0); return givenNameArg.equalsIgnoreCase("testgivenname") ? expected : Collections.emptyList(); }); List<Record1> upperActual = recordSearchService.searchRecord1ByName("TESTGIVENNAME", null); List<Record1> lowerActual = recordSearchService.searchRecord1ByName("testgivenname", null); List<Record1> mixedActual = recordSearchService.searchRecord1ByName("TestGivenName", null); verify(mockRecord1Repo).findByGivenName("TESTGIVENNAME"); verify(mockRecord1Repo).findByGivenName("testgivenname"); verify(mockRecord1Repo).findByGivenName("TestGivenName"); assertEquals(expected, upperActual); assertEquals(expected, lowerActual); assertEquals(expected, mixedActual); }
报错信息
Argument(s) are different! Wanted: record1Repo.findByGivenName( "TESTGIVENNAME" ); Actual invocations have different arguments: record1Repo.findByGivenName( "^\\QTESTGIVENNAME\\E$" ); record1Repo.findByGivenName( "^\\Qtestgivenname\\E$" ); record1Repo.findByGivenName( "^\\QTestGivenName\\E$" );
问题原因
- 断言参数不匹配:服务层的
searchRecord方法会将输入的名字转换为带锚点(^、$)和转义包裹(\Q、\E)的正则表达式字符串,但单元测试的verify断言期望传入原始的名字字符串,两者完全不匹配,导致断言失败。 - 测试逻辑存在漏洞:测试中
when块的判断逻辑是直接比较传入参数与testgivenname是否忽略大小写相等,但实际传入的是正则表达式字符串,这个判断逻辑本不成立,只是因为anyString()的宽泛匹配才没提前暴露问题。
修复方案
方案1:调整verify断言,匹配实际传入的正则字符串
构造与服务层生成逻辑一致的正则字符串,作为verify的预期参数:
@Test void searchRecord1ByGivenName() { // ... 其他代码不变 // 构造服务层会生成的正则字符串 String upperExpectedRegex = "^" + Pattern.quote("TESTGIVENNAME") + "$"; String lowerExpectedRegex = "^" + Pattern.quote("testgivenname") + "$"; String mixedExpectedRegex = "^" + Pattern.quote("TestGivenName") + "$"; verify(mockRecord1Repo).findByGivenName(upperExpectedRegex); verify(mockRecord1Repo).findByGivenName(lowerExpectedRegex); verify(mockRecord1Repo).findByGivenName(mixedExpectedRegex); // ... 断言结果的代码不变 }
方案2:修复when块的逻辑,正确处理正则参数
模拟MongoDB的实际行为,用传入的正则表达式去匹配测试数据中的名字:
@Test void searchRecord1ByGivenName() { Record1 record1 = Record1.builder() .givenName("TestGivenName") .lastName("TestLastName") .build(); List<Record1> expected = List.of(record1); when(mockRecord1Repo.findByGivenName(anyString())).thenAnswer(invocation -> { String regexArg = invocation.getArgument(0); // 编译正则并匹配测试数据的名字 Pattern pattern = Pattern.compile(regexArg, Pattern.CASE_INSENSITIVE); if (pattern.matcher(record1.getGivenName()).matches()) { return expected; } return Collections.emptyList(); }); // ... 调用服务方法的代码不变 // 使用次数验证替代精确参数匹配 verify(mockRecord1Repo, times(3)).findByGivenName(anyString()); // ... 断言结果的代码不变 }
额外说明
如果希望单元测试更贴近生产环境的实际行为,推荐使用方案2,因为它模拟了MongoDB使用正则表达式匹配字段的逻辑,而不是单纯比较字符串。
内容的提问来源于stack exchange,提问作者roice343
相关产品推荐
相关产品推荐

