【问题标题】:Why can't I throw an exception when a Future mocked object's method is executed?为什么执行 Future 模拟对象的方法时不能抛出异常?
【发布时间】:2022-11-03 16:46:18
【问题描述】:

我正在尝试编写一个需要覆盖 catch 块的测试用例。 catch 块捕获InterruptedException 或ExecutionException,两者都在Future 对象的.get() 方法的方法签名中。我正在尝试使用来自 Mockito 的 thenThrow 模拟 InterruptedException,当调用 Future 对象的 .get() 方法时,控件进入未覆盖的 catch 块。

futureData 是 List<Future<Class>> 类型,结果是 Future<Class> 对象。

futureData.forEach(result -> {
    try {
   enginesData.add(result.get()); // the method I am using to force an exception using Mockito
    } catch (InterruptedException | ExecutionException e) {
   // the catch block I am trying to cover
    }
});

下面是我的测试用例:

@Test
public void testInterruptedException () throws ExecutionException, InterruptedException {
    ...
    InterruptedException interruptedException = new InterruptedException("Interrupted Exception");
    when(oneFutureData.get()).thenThrow(CompletableFuture.completedFuture(interruptedException));
    ...
}

我尝试连续编写thenThrow 和doThrow,但它仍然没有覆盖catch 块。可能是因为我试图模拟 Future 对象并模拟 Future 类中的方法吗?

【问题讨论】:

  • futureData 是空的吗?
  • 如果您想从get 电话中接听InterruptedException,那么为什么不在那个电话上简单地thenThrow(interruptedException) 呢?毕竟,catch (InterruptedException 不会赶上CompletableFuture
  • @dan1st 不,它包含一个 List<Future<Class>> 包含两个 Future<Class> 元素。这可能与未来 Mockito 没有检测到被调用的方法有关,因此它没有抛出异常?
  • @racraman 是的,我做到了。我的测试用例上有一个when(futureData.get()).thenThrow(interruptedException)。

标签: java mockito


【解决方案1】:

你需要在你的 // the catch block I am trying to cover - 阻止。 例如,您会调用一些记录器将异常写入日志文件或其他任何内容。
诀窍是您需要监视您在// the catch block I am trying to cover 中调用的任何内容
然后您可以检查是否真的在您的 catch 块中调用了此方法。
查看此示例,了解如何实现此目的:

package de.playground.so74236327;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import static org.mockito.Mockito.*;
// see https://stackoverflow.com/questions/74236327/why-cant-i-throw-an-exception-when-a-future-mocked-objects-method-is-executed
@ExtendWith(MockitoExtension.class) // needed for the @Mock annotation
public class FutureExceptionTest {
    @Mock
    public Future<Class> futureMock;
    @Test
    public void testInterruptedException() throws ExecutionException, InterruptedException {
        MyService mySpy = spy(new MyService());
        ExampleProductionCode exampleProductionCode = new ExampleProductionCode(mySpy);// contains 2 real Future<Class> objects.
        List<Future<Class>> futureData = exampleProductionCode.getFutureData();
        InterruptedException interruptedException = new InterruptedException("Interrupted Exception");
        // Future<Class> oneFutureData = Mockito.mock(Future.class);
        //when(oneFutureData.get()).thenThrow(CompletableFuture.completedFuture(interruptedException)); that doesn't even compile
        when(futureMock.get()).thenThrow(interruptedException);
        futureData.add(futureMock); // add another Future<Class> object, but this time it's just a mock object
        verify(mySpy, times(0)).logError(interruptedException);//nothing has happend yet
        exampleProductionCode.collectAsyncResults(); // calling the 3 Future.get() methods inside
        // 3 cases:
        // 1st: with the completableFuture.completeExceptionally(e);
        // 2nd: FutureTask<Class> throwing an exception inside the task
        // 3rd: comes from the  oneFutureData mock object above
        verify(mySpy, times(3)).logError(any(Exception.class));
    }
}

您的生产代码将类似于以下几行:

package de.playground.so74236327;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class ExampleProductionCode {
    List<Future<Class>> futureData;
    EnginesData enginesData;
    MyService myService;
    public ExampleProductionCode(MyService myService) {
        this.myService = myService;
        futureData = new ArrayList<Future<Class>>();
        enginesData = new EnginesData();
        Future<Class> oneFutureData = createAndStartAsyncTask();
        futureData.add(oneFutureData);
        Future<Class> twoFutureData = createAndStartFutureTask();
        futureData.add(twoFutureData);
    }
    public EnginesData getEngineData() {
        return enginesData;
    }
    public List<Future<Class>> getFutureData() {
        return futureData;
    }
    public Future<Class> createAndStartAsyncTask() {
        CompletableFuture<Class> completableFuture = new CompletableFuture<>();
        Executors.newCachedThreadPool().submit(() -> {
            try {
                Thread.sleep(500);
                // and do whatever you need to do or throw an exception in case something went wrong
                throw new InterruptedException(); // just the exceptional case here for demonstration purposes
                //return null; // return normally if there would be not exception
            } catch (Exception e) {
                completableFuture.completeExceptionally(e);
                // don't call  completableFuture.cancel(false )! that wouldn't work
                // don't call  completableFuture.cancel(true )! that wouldn't work either
            }
        });
        return completableFuture;
    }
    public Future<Class> createAndStartFutureTask() {
        FutureTask<Class> ftask = new FutureTask<Class>(() -> {
            Thread.sleep(500);
            throw new InterruptedException(); // just the exceptional case here for demonstration purposes
            //return MySOClass.class; // return normally if there would be not exception
        });
        ftask.run();
        return ftask;
    }
    public void collectAsyncResults() {
        futureData.forEach(result -> {
            ExampleProductionCode.EnginesData enginesData = this.getEngineData();
            try {
                enginesData.add(result.get()); // the method I am using to force an exception using Mockito
            } catch (InterruptedException | ExecutionException e) {
                // "the catch block I am trying to cover"
                // if you want to cover this catchblock you need to
                // 1. execute some method here, and then
                // 2. spy on this method, so you can check in you test, whether this method was really called with the correct parameters
                myService.logError(e);
            }
        });
    }
    public class EnginesData {
        public void add(Class aclass) {
            //do something
        }
    }
}

和

package de.playground.so74236327;

public class MyService {
        public void logError(Exception e) {
            //do something
        }
}

自己试试: git clone --depth 1 --branch so74236327 git@github.com:bodote/playground.git 查看package de.playground.so74236327 并运行FutureExceptionTest

【讨论】:

    猜你喜欢
    • 2022-01-08
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-15
    • 2011-06-28
    • 1970-01-01
    相关资源
    最近更新 更多