Java mockito使用spy进行更好的异常测试

Java mockito使用spy进行更好的异常测试,java,mockito,junit4,hamcrest,spy,Java,Mockito,Junit4,Hamcrest,Spy,如何进行第三次测试以检查异常消息中是否存在原因1?我还列出了前两个测试中存在的缺点。第一个是不检查消息,第二个是需要大量样板代码 public class CheckExceptionsWithMockitoTest { @Test(expected = RuntimeException.class) public void testExpectedException1() { A a = new A(); a.doSomethingThatTh

如何进行第三次测试以检查异常消息中是否存在原因1?我还列出了前两个测试中存在的缺点。第一个是不检查消息,第二个是需要大量样板代码

public class CheckExceptionsWithMockitoTest {

    @Test(expected = RuntimeException.class)
    public void testExpectedException1() {
        A a = new A();
        a.doSomethingThatThrows();
    }

    @Test
    public void testExpectedException2() {
        A a = new A();
        try {
            a.doSomethingThatThrows();
            fail("no exception thrown");
        } catch (RuntimeException e) {
            assertThat(e.getMessage(), org.hamcrest.Matchers.containsString("cause1"));
        }
    }

    @Test
    public void testExpectedException3() {
        A a = new A();
        A spyA = org.mockito.Mockito.spy(a);
        // valid but doesnt work
        // doThrow(new IllegalArgumentException()).when(spyA).doSomethingThatThrows();
        // invalid but in the spirit of what i want 
        //chekThrow(RuntimeException.class,containsString("cause1")).when(spyA).doSomethingThatThrows();
    }

}
我在Mockito中找不到可以工作的东西,但是有一些东西看起来是可能的(在语法级别)和功能



使用catchexception,我创建了如下测试

import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import org.junit.*;
public class CheckExceptionsWithMockitoTest{  
    //...
    @Test
    public void testExpectedException3() {
        A a = new A();
        verifyException(a,IllegalArgumentException.class)
            .doSomethingThatThrows();
        //if more details to be analized are needed
        assertThat(
            (IllegalStateException) caughtException(),
            allOf(
                is(IllegalStateException.class),
                hasMessageThat(
                        containsString("is not allowed to add counterparties")), 
                hasNoCause()));
        //more asserts could come
        assertNotNull(a);
    }
}
import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import org.junit.*;
public class CheckExceptionsWithMockitoTest{  
    //...
    @Test
    public void testExpectedException3() {
        A a = new A();
        verifyException(a,IllegalArgumentException.class)
            .doSomethingThatThrows();
        //if more details to be analized are needed
        assertThat(
            (IllegalStateException) caughtException(),
            allOf(
                is(IllegalStateException.class),
                hasMessageThat(
                        containsString("is not allowed to add counterparties")), 
                hasNoCause()));
        //more asserts could come
        assertNotNull(a);
    }
}

如果
A
是您的系统正在测试中,那么模仿它是没有意义的,监视它也没有意义。您在
testExpectedException2
中的实现是正确的;样板代码是必需的,因为如果没有
try
块,Java将不会让任何代码在拦截方法后运行(如中所述)

虽然莫基托帮不了什么忙,但朱尼特会的。
@Test(expected=foo)
参数实际上有一个更灵活的选择,即内置:

Mockito将在一个单独的测试中派上用场,该测试检查您的方法是否在保留其消息的同时包装了任意异常,大致如下所示:

@Test
public void doSomethingShouldWrapExceptionWithPassedMessage() {
  Dependency dependency = Mockito.mock(Dependency.class);
  when(dependency.call()).thenThrow(new IllegalArgumentException("quux"));
  A a = new A(dependency);
  thrown.expect(RuntimeException.class);
  thrown.expectMessage(containsString("quux"));
  a.doSomethingThatThrows();
}
小心避免在测试中使用这种常见模式的诱惑。如果捕获到从测试系统抛出的异常,则实际上是将控制权让给SUT的使用者。除了异常的属性和系统的状态之外,在以后的方法中应该没有什么需要测试的了,这两个属性都应该足够少,这样try/catch样板文件就可以理解了。

使用库,或者我猜您正在寻找的解决方案是您的第二个实现

@expected除了它的类之外,不提供对抛出的异常进行断言的任何方法,因此您不能避免尝试/捕获(没有那么多锅炉板代码!)

Mockito没有提供像verifyThrows这样的方法

所以,您可以用try/catching替换一个额外的库:使用,您将能够在一行中捕获异常,并为进一步的断言做好准备

示例源代码 依赖关系
使用catchexception,我创建了如下测试

import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import org.junit.*;
public class CheckExceptionsWithMockitoTest{  
    //...
    @Test
    public void testExpectedException3() {
        A a = new A();
        verifyException(a,IllegalArgumentException.class)
            .doSomethingThatThrows();
        //if more details to be analized are needed
        assertThat(
            (IllegalStateException) caughtException(),
            allOf(
                is(IllegalStateException.class),
                hasMessageThat(
                        containsString("is not allowed to add counterparties")), 
                hasNoCause()));
        //more asserts could come
        assertNotNull(a);
    }
}
import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import org.junit.*;
public class CheckExceptionsWithMockitoTest{  
    //...
    @Test
    public void testExpectedException3() {
        A a = new A();
        verifyException(a,IllegalArgumentException.class)
            .doSomethingThatThrows();
        //if more details to be analized are needed
        assertThat(
            (IllegalStateException) caughtException(),
            allOf(
                is(IllegalStateException.class),
                hasMessageThat(
                        containsString("is not allowed to add counterparties")), 
                hasNoCause()));
        //more asserts could come
        assertNotNull(a);
    }
}

如果您有机会使用scala,scalaTest的fun套件提供了使用intercept()测试异常的简洁方法

这很简单

  test(a list get method catches exceptions){
    intercept[IndexOutBoundsException]{
      spyListObject.get(-1)
    }
  }

如果您正在寻找易于编写/清除的测试,则可以在scala中将测试写入java项目。但这可能带来其他挑战

2015年6月19日的更新答案(如果您使用的是java 8)

使用assertj-core-3.0.0+Java 8 Lambdas

@Test
public void shouldThrowIllegalArgumentExceptionWhenPassingBadArg() {
      assertThatThrownBy(() -> myService.sumTingWong("badArg"))
                                  .isInstanceOf(IllegalArgumentException.class);
}

参考资料:

如果您在Mockito.class中查看spy方法,它将使用spiedInstance创建mock:

 public static <T> T spy(T object) {
    return MOCKITO_CORE.mock((Class<T>) object.getClass(), withSettings()
            .spiedInstance(object)
            .defaultAnswer(CALLS_REAL_METHODS));
}
publicstatict-spy(T对象){
返回MOCKITO_CORE.mock((Class)object.getClass(),with settings()
.spiedInstance(对象)
.defaultAnswer(调用实方法);
}
在MockSettings中,可以注册调用侦听器:

我创建了存储所有报告调用的简单侦听器:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.mockito.listeners.InvocationListener;
import org.mockito.listeners.MethodInvocationReport;

public class StoringMethodInvocationListener implements InvocationListener {

private List<MethodInvocationReport> methodInvocationReports = new ArrayList<>();

@Override
public void reportInvocation(MethodInvocationReport methodInvocationReport) {
    this.methodInvocationReports.add(methodInvocationReport);

}

public List<MethodInvocationReport> getMethodInvocationReports() {
    return Collections.unmodifiableList(methodInvocationReports);
}
import java.util.ArrayList;
导入java.util.Collections;
导入java.util.List;
导入org.mockito.listeners.InvocationListener;
导入org.mockito.listeners.MethodInvocationReport;
公共类StoringMethodInvocationListener实现InvocationListener{
private List methodInvocationReports=new ArrayList();
@凌驾
公共void报告调用(MethodInvocationReport MethodInvocationReport){
this.methodInvocationReports.add(methodInvocationReport);
}
公共列表getMethodInvocationReports(){
返回集合。不可修改列表(methodInvocationReports);
}
}

调用之后,您可以浏览报告,找到所需的报告,并验证存储的throwable是否是预期的报告

例如:

    StoringMethodInvocationListener listener = new StoringMethodInvocationListener();
    Consumer mock2 = mock(Consumer.class, withSettings()
            .spiedInstance(consumerInstance)
            .defaultAnswer(CALLS_REAL_METHODS)
            .invocationListeners(listener));

    try {
        mock2.listen(new ConsumerRecord<String, String>(RECEIVER_TOPIC, 0, 0,  null, "{}"));
    } catch (Exception e){
        //nothing
    }
    Assert.notEmpty(listener.getMethodInvocationReports(), "MethodInvocationReports list must not be empty");
    Assert.isInstanceOf(BindException.class, listener.getMethodInvocationReports().get(1).getThrowable());
StoringMethodInvocationListener=新建StoringMethodInvocationListener();
Consumer mock2=mock(Consumer.class,withSettings()
.spiedInstance(用户实例)
.defaultAnswer(调用\实际\方法)
.调用侦听器(侦听器));
试一试{
mock2.listen(新的消费者记录(RECEIVER_TOPIC,0,0,null,{}”);
}捕获(例外e){
//没什么
}
Assert.notEmpty(listener.getMethodInvocationReports(),“MethodInvocationReports列表不能为空”);
Assert.isInstanceOf(bindeexception.class,listener.getMethodInvocationReports().get(1.getThrowable());

Where do“thrown.expect”和“thrown.expectMessage”来自@Olivier-它们是测试用例顶部声明的
ExpectedException
规则的方法。感谢您的回答。junit需要这种样板代码是可以理解的。我认为有一个真正类的代理是可能的,它将捕获抛出的任何异常,并验证这是否是预期的,并从调用中正常返回。通过这种方式,我可以继续测试,并对状态进行额外的调查(包括异常的类型、内容)。如果它有意义,而mockito中不存在这样的东西,我可以尝试作出贡献。@raiserCost在我个人看来,您的案例非常罕见,解决方法非常简单明了,在mockito中添加该功能没有意义,特别是如果它鼓励了已知的反模式(模拟正在测试的系统)。如果你真的想贡献它,请确保你至少问一下,这样,如果项目所有者看不到添加它的价值,你就不会浪费时间。@raisercostin你当然可以使用JUnit中已有的机制,完全按照你所描述的做。只需在测试中添加更多内容。不过,我不建议这样做-我是此外,我无法想象Mockito团队想要添加一些东西来处理JUnit已经充分满足的案例。
    StoringMethodInvocationListener listener = new StoringMethodInvocationListener();
    Consumer mock2 = mock(Consumer.class, withSettings()
            .spiedInstance(consumerInstance)
            .defaultAnswer(CALLS_REAL_METHODS)
            .invocationListeners(listener));

    try {
        mock2.listen(new ConsumerRecord<String, String>(RECEIVER_TOPIC, 0, 0,  null, "{}"));
    } catch (Exception e){
        //nothing
    }
    Assert.notEmpty(listener.getMethodInvocationReports(), "MethodInvocationReports list must not be empty");
    Assert.isInstanceOf(BindException.class, listener.getMethodInvocationReports().get(1).getThrowable());