无法模拟接收Supplier作为入参的静态方法问题求助
问题
业务代码中调用了RemoteApis的静态方法,流程如下:
var saasManagerApi = RemoteApis.saasSubscriptionApi(saasRegistryServiceUrl, cisLocalBindingToken); var results = Jsons.parser(RemoteApis.call(saasManagerApi::getApplications)) .read("$.applications[?(@.appId =~ /^fos-multitenant-svc.*/)]"); var appName = Jsons.parser(results).read("$[0].appName", String.class); var state = saasManagerV2Service.retrieveSubState(appName, saasManagerApi);
测试时尝试mockRemoteApis静态类,代码如下:
try (MockedStatic<RemoteApis> remoteApisMockedStatic = mockStatic(RemoteApis.class);) { var mockSaasManagerApi = mock(SaasManagerApi.class); remoteApisMockedStatic.when(() -> RemoteApis.saasSubscriptionApi(any(), any())).thenReturn(mockSaasManagerApi); remoteApisMockedStatic.when(() -> RemoteApis.call(mockSaasManagerApi::getApplications)) .thenReturn("Some mocked JSON Response"); }
但测试时RemoteApis.call返回null,换成any()匹配所有参数才能得到预期结果:
remoteApisMockedStatic.when(() -> RemoteApis.call(any())) .thenReturn("Some mocked JSON Response");
由于需要区分不同的Supplier参数(对应不同的API方法调用),不能用any()通配,需要解决精准匹配的问题。
原因
直接使用mockSaasManagerApi::getApplications作为匹配参数时,Mockito会按对象相等性判断参数是否匹配。但业务代码中生成的saasManagerApi::getApplications是一个新的Supplier实例,和测试代码中写的mockSaasManagerApi::getApplications是两个不同的对象,因此匹配失败,返回默认的null。
解决方案
方案1:自定义参数匹配器,验证Supplier的行为
通过argThat传入自定义匹配器,判断传入的Supplier是否对应目标方法的调用:
try (MockedStatic<RemoteApis> remoteApisMockedStatic = mockStatic(RemoteApis.class)) { SaasManagerApi mockSaasManagerApi = mock(SaasManagerApi.class); // 先mock目标方法的返回,避免执行Supplier时抛出异常 when(mockSaasManagerApi.getApplications()).thenReturn(new Object()); remoteApisMockedStatic.when(() -> RemoteApis.saasSubscriptionApi(any(), any())) .thenReturn(mockSaasManagerApi); // 自定义匹配器:执行Supplier,验证是否调用了getApplications方法 remoteApisMockedStatic.when(() -> RemoteApis.call(argThat(supplier -> { try { supplier.get(); verify(mockSaasManagerApi).getApplications(); return true; } catch (Exception e) { return false; } }))).thenReturn("Some mocked JSON Response"); }
方案2:用thenAnswer动态判断参数
通过thenAnswer拦截所有RemoteApis.call调用,根据传入的Supplier执行逻辑返回对应结果:
try (MockedStatic<RemoteApis> remoteApisMockedStatic = mockStatic(RemoteApis.class)) { SaasManagerApi mockSaasManagerApi = mock(SaasManagerApi.class); when(mockSaasManagerApi.getApplications()).thenReturn(new Object()); remoteApisMockedStatic.when(() -> RemoteApis.saasSubscriptionApi(any(), any())) .thenReturn(mockSaasManagerApi); remoteApisMockedStatic.when(() -> RemoteApis.call(any())).thenAnswer(invocation -> { Supplier<?> supplier = invocation.getArgument(0); try { supplier.get(); // 验证是否调用了目标方法 verify(mockSaasManagerApi).getApplications(); return "Some mocked JSON Response"; } catch (VerificationError e) { // 针对其他Supplier返回对应mock结果 return "Other mocked response"; } }); }
方案3:利用反射匹配方法引用(进阶)
如果需要更精准的匹配,可以通过反射获取Supplier对应的方法信息:
remoteApisMockedStatic.when(() -> RemoteApis.call(argThat(supplier -> { // 获取Supplier的target和method Field targetField = supplier.getClass().getDeclaredField("target"); targetField.setAccessible(true); Object target = targetField.get(supplier); Field methodField = supplier.getClass().getDeclaredField("method"); methodField.setAccessible(true); Method method = (Method) methodField.get(supplier); return target == mockSaasManagerApi && method.getName().equals("getApplications"); }))).thenReturn("Some mocked JSON Response");
注意:这种方式依赖于方法引用的底层实现(不同JDK版本可能有差异),建议优先使用前两种方案。
内容的提问来源于stack exchange,提问作者Prabal Rakshit
相关产品推荐
相关产品推荐

