使用 Mockito 从模拟中抛出已检查的异常

Posted

技术标签:

【中文标题】使用 Mockito 从模拟中抛出已检查的异常【英文标题】:throw checked Exceptions from mocks with Mockito 【发布时间】:2011-04-15 07:00:48 【问题描述】:

我试图让我的一个模拟对象在调用特定方法时抛出一个检查异常。我正在尝试以下方法。

@Test(expectedExceptions = SomeException.class)
public void throwCheckedException() 
    List<String> list = mock(List.class);
    when(list.get(0)).thenThrow(new SomeException());
    String test = list.get(0);


public class SomeException extends Exception 

但是,这会产生以下错误。

org.testng.TestException: 
Expected exception com.testing.MockitoCheckedExceptions$SomeException but got org.mockito.exceptions.base.MockitoException: 
Checked exception is invalid for this method!
Invalid: com.testing.MockitoCheckedExceptions$SomeException

看Mockito documentation,他们只使用RuntimeException,难道不能用Mockito从模拟对象抛出检查异常吗?

【问题讨论】:

【参考方案1】:

检查 List 的 Java API。get(int index) 方法被声明为仅抛出扩展 RuntimeExceptionIndexOutOfBoundException。 您正试图告诉 Mockito 抛出一个异常 SomeException(),该异常该特定方法调用无法抛出该异常

进一步澄清。List 接口不提供从 get(int index) 方法抛出的已检查异常,这就是 Mockito 失败的原因。 当您创建 mocked List 时,Mockito 将使用 List 的定义.class 来创建它的 mock。

您使用when(list.get(0)).thenThrow(new SomeException()) 指定的行为与List API 中的方法签名不匹配,因为get(int index) 方法不会抛出SomeException() 所以Mockito 失败了。

如果你真的想这样做,那么让 Mockito 抛出一个 new RuntimeException() 或者更好地抛出一个 new ArrayIndexOutOfBoundsException(),因为 API 指定这是唯一要抛出的有效异常。

【讨论】:

虽然我的真实代码实际上并未使用 List,但您的答案也适用于该方法调用。我在嘲笑错误的方法。谢谢。 额外:如果你抛出一个没有任何 throwable 的方法,Mocktio 不会抱怨,但你也会得到这个异常 对于 Kotliner:Kotlin 没有检查异常,因此您通常不能(在函数签名中)声明函数抛出异常。但是,您可以使用 Throws 注释对函数进行注释,以使编译器生成与在等效 Java 代码中声明 throws 相同的字节码。请参阅 [此处] (kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-throws/…) 了解更多详情。 自 Mockito 2.11.0 (see 2.10.3) 发布以来强制执行此检查。 同样的逻辑是否也适用于 Scala?【参考方案2】:

一种解决方法是使用willAnswer() 方法。

例如,使用BDDMockito 进行以下工作(并且不会抛出MockitoException,但实际上会根据此处的要求抛出已检查的Exception):

given(someObj.someMethod(stringArg1)).willAnswer( invocation ->  throw new Exception("abc msg"); );

普通 Mockito 的等价物将使用 doAnswer 方法

【讨论】:

或者当方法返回void时使用willAnswer( invocation -&gt; throw new Exception("abc msg"); ).given(someObj).someMethod(stringArg1); 或使用 when(someObj.someMethod(stringArg1)).thenAnswer(invocation -> throw new Exception("abc msg"); ); 很好的解决方法,谢谢!对于想要 (1) 无缝地将其用作扩展功能并且 (2) 能够传递多个参数(如 willThrow() 通常允许)的 Kotliner,我写了一个 Gist【参考方案3】:

有 Kotlin 的解决方案:

given(myObject.myCall()).willAnswer 
    throw IOException("Ooops")

给定来自哪里

导入 org.mockito.BDDMockito.given

【讨论】:

这也适用于 Java .thenAnswer((t) -&gt; throw new IOException(); );【参考方案4】:

请注意,一般来说,Mockito 确实允许抛出已检查的异常,只要在消息签名中声明了异常。例如,给定

class BarException extends Exception 
  // this is a checked exception


interface Foo 
  Bar frob() throws BarException

写法是合法的:

Foo foo = mock(Foo.class);
when(foo.frob()).thenThrow(BarException.class)

但是,如果你抛出一个未在方法签名中声明的检查异常,例如

class QuxException extends Exception 
  // a different checked exception


Foo foo = mock(Foo.class);
when(foo.frob()).thenThrow(QuxException.class)

Mockito 将在运行时失败,并带有一些误导性的通用消息:

Checked exception is invalid for this method!
Invalid: QuxException

这可能会让您认为通常不支持已检查的异常,但实际上 Mockito 只是想告诉您 已检查的异常对于此方法无效强>。

【讨论】:

【参考方案5】:

这在 Kotlin 中适用于我:

when(list.get(0)).thenThrow(new ArrayIndexOutOfBoundsException());

注意:抛出除 Exception() 之外的任何已定义异常

【讨论】:

正是我想要的,可以抛出除Exception以外的任何异常

以上是关于使用 Mockito 从模拟中抛出已检查的异常的主要内容,如果未能解决你的问题,请参考以下文章

JLS 的哪些部分证明能够像未经检查一样抛出已检查异常?

Spring Security 部署失败:生命周期方法 [initialize] 不得抛出已检查异常

终端操作(例如 forEach)可以重新抛出已检查的异常吗?

从 lambda 表达式中抛出的已检查异常

Mockito 如何模拟和断言抛出的异常?

在模拟器中正常,但在 ios 设备中抛出异常