You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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$"
);

问题原因

  1. 断言参数不匹配:服务层的searchRecord方法会将输入的名字转换为带锚点(^、$)和转义包裹(\Q、\E)的正则表达式字符串,但单元测试的verify断言期望传入原始的名字字符串,两者完全不匹配,导致断言失败。
  2. 测试逻辑存在漏洞:测试中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:35:57