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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 05:08:10