如何在 JMockit 中正确模拟私有 ExecutorService 的提交方法

How to properly mock a private ExecutorService's submit method in JMockit

我有一个 class,其中包含一个私有 ExecutorService 实例。在 class 中,我有一个运行提交方法并捕获 RejectedExecutionException 的方法。但是,我在模拟 ExecutorService 实例以抛出异常以便完成测试覆盖时遇到了麻烦。我正在使用 JMockit 1.45。

我已经浏览过 JMockit 教程和其他站点;无论我使用@Mocked、@Capturing,还是创建一个新的假 class,它似乎都不起作用。

// Implemented Class:
public class TaskRegister {

    private ExecutorService executor;

    public TaskRegister() {
        this.executor = Executors.newFixedThreadPool(5);
    }

    public void executeTask(Runnable task) {
        try {
            this.executor.submit(task);
        } catch (RejectedExecutionException e) {
            System.out.println(e.getMessage);
        }
    }
}


// Unit Test Class:
public class TestTaskRegister {
    @Tested
    private TaskRegister tested;

    private static int counter;

    @Test // this works
    public void runNormalTask() throws InterruptedException {
        counter = 0;
        Runnable mockTask = new Runnable() {
            counter++;
        }

        tested.executeTask(mockTask);
        Thread.sleep(100); // Allow executor to finish other thread.
        assertEquals(1, counter);
    }

    @Test // this doesn't work, will have missing invocation error.
    public void throwsError (@Capturing ExecutorService executor) throws InterruptedException {
        counter = 0;

        // somehow the tested class still runs the actual executor 
        // and not the mocked one.
        new Expectations() {{
             executor.submit((Runnable) any);
             result = new RejectedExecutionException();
        }};

        Runnable mockTask = new Runnable() {
            // some task
        }

        tested.executeTask(mockTask);
        Thread.sleep(100);
        assertEquals(0, counter);
    }
}

我希望 @Capturing 拦截真正的执行程序实现并在 executor.submit 被调用时抛出异常,但它并没有这样做。

@Capturing 的模拟可能代价高昂,并且在某些情况下可能会导致意外结果,因此(目前)所有 java.* 类 都被排除在外。因此,java.util.concurrent.ThreadPoolExecutor 不会在此测试中被嘲笑(它可以与 @Mocked 一起)。

实际上,RejectedExecutionException 异常永远不会发生(至少 ThreadPoolExecutor 不会发生——可能只有 ForkJoinPool 才会发生。所以,这个测试不值得付出努力。事实上,由于该异常是 RuntimeException,您可以简单地完全删除 catch 块。

这是(滥用)使用模拟库时发生的坏事之一:人们有时会使用它们来测试不可能的情况,因此编写无用的测试。

您可以使用参数 Executor 创建一个新的构造函数。
因此,您的 class 将更具可配置性(因为目前您只能使用一种执行程序)并且您将能够通过使用模拟执行程序手动实例化它来测试 class。

public class TaskRegister {

    private ExecutorService executor;

    public TaskRegister(ExecutorService executor) {
        this.executor = executor;
    }

    public TaskRegister() {
        this(Executors.newFixedThreadPool(5));
    }

    public void executeTask(Runnable task) {
        try {
            this.executor.submit(task);
        } catch (RejectedExecutionException e) {
            System.out.println(e.getMessage);
        }
    }
}