Mockito静态Mock在CompletableFuture.supplyAsync中失效的问题求助
静态Mock在CompletableFuture场景下失效的问题
我编写了示例代码,在不同的CompletableFuture中调用工具类的静态方法,然后合并结果。但编写JUnit测试时,即使对工具类做了静态Mock,调用仍会进入真实方法;移除CompletableFuture实现后,Mock能正常工作。请问需要调整Mock方式吗?
CompletableFuture示例代码
package com.demo.futures; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CompletableFutureSample { public List<String> myShortTaskWithoutCompletableFuture() { System.out.println("Inside myShortTaskWithoutCompletableFuture"); List<String> res = new ArrayList<>(); res.add(MyUtils1.getSampleValue1("Ranesh")); return res; } public List<String> myShortTask() { System.out.println("Inside myShortTask"); List<CompletableFuture<String>> futures = new ArrayList<>(); List<String> res = new ArrayList<>(); CompletableFuture<String> future1 = CompletableFuture .supplyAsync(() -> MyUtils1.getSampleValue1("Ranesh")); futures.add(future1); try { CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); for (CompletableFuture<String> future : futures) { res.add(future.get()); } } catch (InterruptedException | ExecutionException e) { System.out.println(e.getMessage()); } return res; } public List<String> myTask() { List<CompletableFuture<String>> futures = new ArrayList<>(); List<String> res = new ArrayList<>(); CompletableFuture<String> future1 = CompletableFuture .supplyAsync(() -> MyUtils1.getSampleValue1("Ranesh")); CompletableFuture<String> future2 = CompletableFuture .supplyAsync(() -> MyUtils2.getSampleValue2("Ranesh")); CompletableFuture<String> future3 = CompletableFuture .supplyAsync(() -> MyUtils3.getSampleValue3("Ranesh")); futures.add(future1); futures.add(future2); futures.add(future3); try { CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); for (CompletableFuture<String> future : futures) { res.add(future.get()); } } catch (InterruptedException | ExecutionException e) { System.out.println(e.getMessage()); } return res; } }
工具类代码
package com.demo.futures; public class MyUtils1 { public static String getSampleValue1(String str) { System.out.println("Inside My Utils 1"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } return "Hello : " + str; } }
JUnit测试类代码
package com.demo.futures; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import java.util.List; import static org.mockito.ArgumentMatchers.anyString; @ExtendWith(MockitoExtension.class) class CompletableFutureSampleTest { @InjectMocks private CompletableFutureSample completableFutureSample; private MockedStatic<MyUtils1> myUtils1MockedStatic; @BeforeEach void setup() { completableFutureSample = new CompletableFutureSample(); myUtils1MockedStatic = Mockito.mockStatic(MyUtils1.class); } @Test void testMyShortTaskWithoutCompletableFuture() { myUtils1MockedStatic.when(() -> MyUtils1.getSampleValue1(anyString())).thenReturn("Test String Result"); List<String> res = completableFutureSample.myShortTaskWithoutCompletableFuture(); Assertions.assertEquals(1, res.size()); Assertions.assertEquals("Test String Result", res.get(0)); } @Test void testMyShortTask() { myUtils1MockedStatic.when(() -> MyUtils1.getSampleValue1(anyString())).thenReturn("Test String Result"); List<String> res = completableFutureSample.myShortTask(); Assertions.assertEquals(1, res.size()); Assertions.assertEquals("Test String Result", res.get(0)); } @Test void testMyTask() { } @AfterEach void cleanUp() { myUtils1MockedStatic.close(); } }
问题原因及解决方法
原因
CompletableFuture.supplyAsync()默认使用ForkJoinPool的公共线程池执行任务,而Mockito的静态Mock是线程绑定的——Mock仅在创建它的测试主线程生效,异步任务在其他线程执行时无法识别到Mock,因此会调用真实方法。
解决方法
方法1:控制异步任务的执行线程
创建自定义单线程Executor,让异步任务在可控线程中执行,确保Mock作用域覆盖该线程。可以修改被测方法允许传入Executor,或者在测试中拦截supplyAsync调用指定自定义线程池:
@Test void testMyShortTask() { ExecutorService executor = Executors.newSingleThreadExecutor(); try { myUtils1MockedStatic.when(() -> MyUtils1.getSampleValue1(anyString())).thenReturn("Test String Result"); // 拦截CompletableFuture.supplyAsync,指定自定义线程池 MockedStatic<CompletableFuture> cfMock = Mockito.mockStatic(CompletableFuture.class); cfMock.when(() -> CompletableFuture.supplyAsync(Mockito.any(Supplier.class))) .thenAnswer(invocation -> CompletableFuture.supplyAsync(invocation.getArgument(0), executor)); List<String> res = completableFutureSample.myShortTask(); Assertions.assertEquals(1, res.size()); Assertions.assertEquals("Test String Result", res.get(0)); cfMock.close(); } finally { executor.shutdown(); } }
更优雅的方式是修改被测代码,允许注入Executor,测试时传入单线程池,从根源上控制任务执行环境。
方法2:创建跨线程生效的静态Mock
通过Mockito的配置,让静态Mock对所有线程生效(注意:可能引发多线程测试的状态污染,需谨慎使用):
@BeforeEach void setup() { completableFutureSample = new CompletableFutureSample(); // 创建跨线程生效的静态Mock myUtils1MockedStatic = Mockito.mockStatic(MyUtils1.class, Mockito.withSettings().strictness(Strictness.LENIENT)); }
额外建议
尽量避免在异步任务中直接调用静态方法,可将静态方法封装为实例依赖,通过依赖注入传入。这样测试时只需Mock实例对象,无需处理静态Mock的线程问题,代码可测试性更高:
// 封装工具类为实例服务 public class MyUtils1Service { public String getSampleValue1(String str) { return MyUtils1.getSampleValue1(str); } } // 在CompletableFutureSample中注入实例 public class CompletableFutureSample { private final MyUtils1Service myUtils1Service; public CompletableFutureSample(MyUtils1Service myUtils1Service) { this.myUtils1Service = myUtils1Service; } public List<String> myShortTask() { // ... CompletableFuture<String> future1 = CompletableFuture .supplyAsync(() -> myUtils1Service.getSampleValue1("Ranesh")); // ... } }
内容的提问来源于stack exchange,提问作者user9828728
相关产品推荐
相关产品推荐

