Mockito匹配Spring ReactiveRedisTemplate重载方法可变参数的问题求助
服务中的deleteObject方法
服务中实现了基于ReactiveRedisTemplate的批量删除方法:
private final ReactiveRedisTemplate<String, String> reactiveRedisTemplate; public Mono<Boolean> deleteObject(String... keys) { return reactiveRedisTemplate.delete(keys).map(c -> c > 0); }
ReactiveRedisTemplate的delete重载实现
ReactiveRedisTemplate内部有两个签名不同的delete方法:
@SafeVarargs public final Mono<Long> delete(K... keys) { Assert.notNull(keys, "Keys must not be null!"); Assert.notEmpty(keys, "Keys must not be empty!"); Assert.noNullElements(keys, "Keys must not contain null elements!"); if (keys.length == 1) { // 省略实现 } // 省略实现 } public Mono<Long> delete(Publisher<K> keys) { Assert.notNull(keys, "Keys must not be null!"); return this.doCreateFlux((connection) -> { // 省略实现 }); }
测试代码及初始报错
执行以下测试代码时出现NullPointerException,推测是可变参数匹配失败:
var key = "key"; when(reactiveRedisTemplate.delete(key)).thenReturn(Mono.just(1L)); StepVerifier.create(service.deleteObject(key)) .expectNextMatches(c -> c) .verifyComplete(); verify(this.reactiveRedisTemplate, times(1)).delete(key);
尝试的匹配器及报错
尝试多种ArgumentMatcher方式均报错:
使用
any()匹配:when(reactiveRedisTemplate.delete(ArgumentMatchers.<String>any())).thenReturn(Mono.just(1L));报错:
java.lang.IllegalArgumentException: Keys must not contain null elements!使用
eq()匹配数组:when(reactiveRedisTemplate.delete(eq(new String[] { key }))).thenReturn(Mono.just(1L));报错:
java.lang.IllegalArgumentException: Keys must not be null!使用
anyString()匹配:when(reactiveRedisTemplate.delete(anyString())).thenReturn(Mono.just(1L));报错:
java.lang.NullPointerException(匹配失败)自定义可变参数匹配器:
public class StringVarargsMatcher implements VarargMatcher, ArgumentMatcher<String[]> { private String[] expectedValues; public StringVarargsMatcher(String... expectedValues) { Arrays.sort(expectedValues); this.expectedValues = expectedValues; } @Override public boolean matches(String[] strings) { Arrays.sort(strings); return Arrays.equals(strings, expectedValues); } } when(reactiveRedisTemplate.delete(argThat(new StringVarargsMatcher(key)))).thenReturn(Mono.just(1L));报错:
java.lang.IllegalArgumentException: Keys must not be null!
核心问题是:使用when(mock.method(...))时,Mockito会调用真实的方法逻辑(这里是ReactiveRedisTemplate的delete方法),触发了内部的Assert断言检查,导致报错。改用doReturn可以绕过真实方法的执行,直接指定返回值。
方法1:用doReturn绕过断言
var key = "key"; // 方式1:匹配任意String数组参数 doReturn(Mono.just(1L)).when(reactiveRedisTemplate).delete(any(String[].class)); // 方式2:精确匹配指定参数数组 // doReturn(Mono.just(1L)).when(reactiveRedisTemplate).delete(eq(new String[]{key})); StepVerifier.create(service.deleteObject(key)) .expectNextMatches(c -> c) .verifyComplete(); // 验证时同样用数组匹配 verify(reactiveRedisTemplate, times(1)).delete(eq(new String[]{key}));
方法2:用varargThat精确匹配(Mockito 3+)
如果需要更灵活的参数匹配,结合doReturn使用varargThat:
var key = "key"; doReturn(Mono.just(1L)).when(reactiveRedisTemplate) .delete(varargThat(keys -> Arrays.equals(keys, new String[]{key}))); StepVerifier.create(service.deleteObject(key)) .expectNextMatches(c -> c) .verifyComplete(); verify(reactiveRedisTemplate, times(1)) .delete(varargThat(keys -> Arrays.equals(keys, new String[]{key})));
原理说明
doReturn是Mockito的“存根”方式,它不会调用mock对象的真实方法,只是告诉Mockito当指定方法被调用时返回什么值。而when(mock.method(...))会先调用一次真实方法来获取方法引用,这就触发了ReactiveRedisTemplate中delete方法的参数断言,导致各种IllegalArgumentException或NullPointerException。
内容的提问来源于stack exchange,提问作者mpartan

