【问题标题】:Using junit @Rule, expectCause() and hamcrest matchers使用 junit @Rule、expectCause() 和 hamcrest 匹配器
【发布时间】:2014-04-16 06:40:10
【问题描述】:

我有一个测试:

@Rule
public ExpectedException thrown = ExpectedException.none();
...
@Test
public void testMethod()
{
    final String error = "error message";
    Throwable expectedCause = new IllegalStateException(error);
    thrown.expectCause(org.hamcrest.Matchers.<Throwable>equalTo(expectedCause));
    someServiceThatTrowsException.foo();
}

当通过 mvn 测试方法运行时,我得到了错误:

java.lang.NoSuchMethodError: org.junit.rules.ExpectedException.expectCause(Lorg/hamcrest/Matcher;)V

测试编译正常。

请帮帮我,不明白如何测试异常原因?

【问题讨论】:

  • 我目前有同样的问题......对我来说,当我使用从父项目继承的依赖项时会出现问题,但现在当我在本地 POM.xml 中重新声明依赖项时。你找到解决办法了吗??
  • @Dennis,不,我没有。我使用标准的 try/catch java 习惯用法。如果没有抛出异常,则调用 junit fail()。在 catch 块中我分析了原因。
  • 谢谢。我现在会坚持我的重复依赖,但会像你所做的那样回退到一个 try-catch 块。

标签: java unit-testing maven junit


【解决方案1】:

验证 Kotlin 语言抛出异常的消息和原因的示例:

@get:Rule
val exceptionRule: ExpectedException = ExpectedException.none()

@Test
fun `test method`() {
    exceptionRule.expect(NestedServletException::class.java)
    exceptionRule.expectMessage("error msg")
    exceptionRule.expectCause(instanceOf(IllegalStateException::class.java))
    // ...

【讨论】:

  • 虽然有用,但问题与 Kotlin 无关,并且此答案未解决所询问的错误
【解决方案2】:

您可以使用内置匹配器 org.hamcrest.Matchers.instanceOforg.junit.internal.matchers.ThrowableMessageMatcher.hasMessage 完全做到这一点:

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.both;
import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage;

public class temp {
    @Rule
    public ExpectedException expectedException = ExpectedException.none();

    @Test
    public void youCannotDivideByZero() {
        expectedException.expect(RuntimeException.class);
        expectedException.expectMessage(equalTo("Division exception"));
        expectedException.expectCause(both(hasMessage(equalTo("/ by zero"))).and(instanceOf(ArithmeticException.class)));
        divide(1, 0);
    }

    private float divide(int first, int second) {
        try {
            return first / second;
        } catch(ArithmeticException e) {
            throw new RuntimeException("Division exception", e);
        }
    }
}

【讨论】:

    【解决方案3】:

    总结一下。

    使用 JUnit 4(hamcrest 1.3,请注意,JUnit 4 依赖于不包含 org.hamcrest.beans 包的 hamcrest-core)

    所以,你需要导入:

    <dependency>
      <groupId>org.hamcrest</groupId>
      <artifactId>hamcrest-all</artifactId>
      <version>1.3</version>
      <scope>test</scope>
    </dependency>
    

    代码:

    import static org.hamcrest.CoreMatchers.*;
    import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
    
    @Rule
    public ExpectedException expectedException = ExpectedException.none();
    
    @Test
    public void testThatThrowsNiceExceptionWithCauseAndMessages(){
    
      expectedException.expect(RuntimeException.class );
      expectedException.expectMessage("Exception message");                                           
      expectedException.expectCause(
        allOf(
          isA(IllegalStateException.class),
          hasProperty("message", is("Cause message"))
        )
      );
    
      throw 
        new RuntimeException("Exception message", 
          new IllegalStateException("Cause message"));
    }
    

    【讨论】:

      【解决方案4】:

      导入

      <dependency>
        <groupId>it.ozimov</groupId>
        <artifactId>java7-hamcrest-matchers</artifactId>
        <version>1.3.0</version>
        <scope>test</scope>
      </dependency>
      

      然后:

      @Rule
      public ExpectedException thrown = ExpectedException.none();
      ...
      @Test
      public void testMethod()
      {
          final String errorMessage = "error message";
          Class<? extends Throwable> expectedCause = IllegalStateException.class;
          thrown.expectCause(ExpectedException.exceptionWithMessage(expectedCause, errorMessage));
          someServiceThatTrowsException.foo();
      }
      

      它也适用于原因的子类型。在其他解决方案中,我观察到它们接受超类型,我认为这是错误的。

      消息必须相等或包含在原因的错误消息中。

      【讨论】:

        【解决方案5】:

        通常我更喜欢以下结构:

        expectedException.expectCause(isA(NullPointerException.class));

        【讨论】:

        • 最好添加该方法的来源:import static org.hamcrest.CoreMatchers.isA;
        【解决方案6】:

        hamcrest 的 any(Class) 匹配器运行良好:

        @Rule
        public ExpectedException thrown = ExpectedException.none();
        ...
        @Test
        public void testMethod()
        {
            thrown.expect(RuntimeException.class);
            thrown.expectCause(org.hamcrest.Matchers.any(IllegalStateException.class));
        }
        

        【讨论】:

          【解决方案7】:

          稍微简单地介绍一下静态导入并检查类和原因异常的消息:

          import static org.hamcrest.Matchers.allOf;
          import static org.hamcrest.Matchers.hasProperty;
          import static org.hamcrest.Matchers.instanceOf;
          import static org.hamcrest.Matchers.is;
          import static org.junit.Assert.assertThat;
          
          @Test
          public void testThatThrowsNiceExceptionWithCauseAndMessages(){
          
               expectedException.expect(RuntimeException.class );
               expectedException.expectMessage("Exception message");                                           
               expectedException.expectCause(allOf(instanceOf(IllegalStateException.class),
                                                  hasProperty("message", is("Cause message"))) );
          
               throw new RuntimeException("Exception message", new IllegalStateException("Cause message"));
          }
          

          您甚至可以使用 hasProperty 匹配器来断言嵌套原因或测试“getLocalizedMessage”方法。

          【讨论】:

            【解决方案8】:

            JUnit 版本问题。

            ExpectedException.expectCause()4.11.

            4.10 或更低版本中没有这种方法。

            您应该确保您的运行时 JUnit 版本 >= 4.11,与您的编译版本相同。

            【讨论】:

            • 我的意思是你的runtime JUnit版本,你可以试试调试代码
            【解决方案9】:

            您可以使用此处所述的自定义匹配器 (http://www.javacodegeeks.com/2014/03/junit-expectedexception-rule-beyond-basics.html) 来测试异常原因。

            自定义匹配器

            private static class CauseMatcher extends TypeSafeMatcher<Throwable> {
            
                private final Class<? extends Throwable> type;
                private final String expectedMessage;
            
                public CauseMatcher(Class<? extends Throwable> type, String expectedMessage) {
                    this.type = type;
                    this.expectedMessage = expectedMessage;
                }
            
                @Override
                protected boolean matchesSafely(Throwable item) {
                    return item.getClass().isAssignableFrom(type)
                            && item.getMessage().contains(expectedMessage);
                }
            
                @Override
                public void describeTo(Description description) {
                    description.appendText("expects type ")
                            .appendValue(type)
                            .appendText(" and a message ")
                            .appendValue(expectedMessage);
                }
            }
            

            测试用例

            @Rule
            public ExpectedException thrown = ExpectedException.none();
            
            @Test
            public void verifiesCauseTypeAndAMessage() {
                thrown.expect(RuntimeException.class);
                thrown.expectCause(new CauseMatcher(IllegalStateException.class, "Illegal state"));
            
                throw new RuntimeException("Runtime exception occurred",
                        new IllegalStateException("Illegal state"));
            }
            

            【讨论】:

              【解决方案10】:

              试试这个方法:

              @Rule public ExpectedException thrown = ExpectedException.none();
              
              @Test public void testMethod() throws Throwable {
                  final String error = "error message";
                  Throwable expectedCause = new IllegalStateException(error);
                  thrown.expectCause(IsEqual.equalTo(expectedCause));
                  throw new RuntimeException(expectedCause);
              }
              

              考虑不要通过 equals 而是通过 IsInstanceOf 和/或在必要时合并异常消息来检查原因。通过 equals 比较原因也检查堆栈跟踪,这可能比您想要测试/检查的要多。比如这样:

              @Rule public ExpectedException thrown = ExpectedException.none();
              
              @Test public void testMethod() throws Throwable {
                  final String error = "error message";
                  thrown.expectCause(IsInstanceOf.<Throwable>instanceOf(IllegalStateException.class));
                  thrown.expectMessage(error);
                  throw new RuntimeException(new IllegalStateException(error));
              }
              

              【讨论】:

              • 第二个例子是正确的方法。
              • thrown.expectCause(IsInstanceOf.instanceOf(java.io.IOException.class)); 可以,因为推断出
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2015-01-02
              • 1970-01-01
              • 2018-10-07
              • 1970-01-01
              • 1970-01-01
              • 2017-04-28
              • 2015-09-15
              相关资源
              最近更新 更多