【问题标题】:mockito better expected exception test using spymockito 使用 spy 进行更好的预期异常测试
【发布时间】:2013-07-21 13:42:08
【问题描述】:

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

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);
    }
}

【问题讨论】:

    标签: java mockito junit4 hamcrest spy


    【解决方案1】:

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

    @expected 没有提供任何方法来断言抛出的异常,除了它的类,所以你不能避免尝试/捕捉(没有那么多样板代码!)

    Mockito 不提供类似 verifyThrows 方法的东西。

    因此,您可以用 try/catch 换取额外的库:使用 catch-exception,您将能够在一行中捕获异常,并为进一步的断言做好准备。

    示例源代码

    A a = new A();
    
    when(a).doSomethingThatThrows();
    
    then(caughtException())
            .isInstanceOf(IllegalStateException.class)
            .hasMessageContaining("is not allowed to add counterparties")
            .hasNoCause();
    

    依赖关系

    'com.googlecode.catch-exception:catch-exception:1.2.0'
    

    【讨论】:

      【解决方案2】:

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

      虽然 Mockito 不会有任何帮助,但 JUnit 会。 @Test(expected=foo) 参数其实有一个更灵活的替代方案,内置的ExpectedException JUnit rule

      public class CheckExceptionsWithMockitoTest {
      
        @Rule public ExpectedException thrown = ExpectedException.none();
      
        @Test
        public void testExpectedException1() {
          A a = new A();
          thrown.expect(RuntimeException.class);
          thrown.expectMessage(containsString("cause1"));
          a.doSomethingThatThrows();
        }
      }
      

      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 样板是可以原谅的。

      【讨论】:

      • “thrown.expect”和“thrown.expectMessage”从何而来??
      • @Olivier - 它们是在测试用例顶部声明的ExpectedException 规则的方法。
      • 感谢您的回答。 junit 需要该样板代码是可以理解的。我在想可以有一个真实类的代理,它将捕获任何抛出的异常,验证是否这是预期的,并从调用中正常返回。通过这种方式,我可以继续测试并进一步调查状态(包括异常的类型、内容)。如果它有意义并且在 mockito 中不存在这样的东西,我可以尝试做出贡献。
      • @raisercostin 我个人的看法是,您的情况非常少见——而且解决方法足够小且简单——在 Mockito 中添加该功能没有意义,特别是如果它鼓励已知的反-pattern(模拟被测系统)。如果您确实想贡献它,请确保您至少询问Mockito development mailing list,这样如果项目所有者没有看到添加它的价值,您就不会浪费时间。
      • @raisercostin 您当然可以完全按照您的描述使用 JUnit 中已有的机制。只需在测试中添加更多内容。不过我不推荐它——我是“一个断言,一个测试用例”旅的成员。此外,我无法想象 Mockito 团队想要添加一些东西来处理 JUnit 已经充分满足的情况。
      【解决方案3】:

      如果你有机会使用 scala,scalaTest 的有趣套件有使用拦截 (http://www.scalatest.org/getting_started_with_fun_suite) 测试异常的简洁方法。

      就这么简单

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

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

      【讨论】:

        【解决方案4】:

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

        使用 assertj-core-3.0.0 + Java 8 Lambdas

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

        参考:http://blog.codeleak.pl/2015/04/junit-testing-exceptions-with-java-8.html

        【讨论】:

          【解决方案5】:

          使用 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);
              }
          }
          

          【讨论】:

            【解决方案6】:

            如果您查看 Mockito.class on spy 方法,它会使用 spiedInstance 创建模拟:

             public static <T> T spy(T object) {
                return MOCKITO_CORE.mock((Class<T>) object.getClass(), withSettings()
                        .spiedInstance(object)
                        .defaultAnswer(CALLS_REAL_METHODS));
            }
            

            在 MockSettings 中可以注册调用监听器:https://static.javadoc.io/org.mockito/mockito-core/3.0.0/org/mockito/listeners/InvocationListener.html

            我创建了一个简单的监听器来存储所有报告的调用:

            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);
            }
            

            }

            调用后,您可以查看报告并找到需要的报告,并验证存储的 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());
            

            【讨论】:

              猜你喜欢
              • 2019-01-23
              • 2017-03-06
              • 1970-01-01
              • 2015-08-09
              • 2011-07-31
              • 2019-07-28
              • 1970-01-01
              • 1970-01-01
              • 2017-11-17
              相关资源
              最近更新 更多