我的@Rule 如何与 ExpectedException 规则一起工作?

How can my @Rule work together ExpectedException rule?

我创建了一个类似于@RunWith parameterized.class 的@Rule 以便所有测试用例都可以使用不同的参数重复测试(我不能直接使用parameterized.class 因为我的测试class 已经有@ Runwith 用于其他目的)。

但是,@Rule 在测试以下方法时不起作用:

@Test
public void fooTest() {
/*exception is an ExceptedException initialized somewhere else as:
 *@Rule public ExpectedException exception = ExpectedException.none();
 */
    exception.expect(A);
    exception.expectMessage(B);
    someTestWhichThrowExceptionOfB(); 
}

事实上,如果我将我的参数硬编码为一个值,测试就会通过,因为它确实抛出了 B 的异常。 但是,如果我设置我的参数 = MyParameterRule.value(),测试也会抛出 B 的异常,但随后失败并说它失败是因为 B 的异常?

我想在第二种情况下,如果我使用 MyParameterRule,那么异常不起作用?那么为什么?如何让它继续工作?

如果您可以依赖 JUnit 4.12,则可以将 Parameterized@UseParametersRunnerFactory 一起使用(有关详细信息,请参阅 the Parameterized Javadoc)。

至于为什么你的参数化规则不起作用,这里有一个(有点长)解释。

JUnit 有一个内部假设,即为每个测试方法创建一个新的测试实例 class。 JUnit 这样做是为了使来自一个测试方法 运行 的测试实例中存储的状态不会影响下一个测试方法 运行.

ExpectedException规则具有相同的期望。当您调用 expect 时,它会更改在该字段初始化时创建的 ExpectedException 字段的状态。它将其修改为 "expect" 要抛出的异常。

当您的规则尝试 运行 违反此假设的相同方法两次(使用不同的参数)。第一次调用调用 expect 的方法时它会起作用,但当您再次调用它时,它可能不起作用,因为您正在重新使用先前修改的 ExpectedException 规则。

当 JUnit 运行 是 JUnit4 样式测试的测试方法时,它会执行以下操作:

  1. 创建测试实例class。
  2. 创建一个 Statement 将 运行 测试方法
  3. 如果方法的 @Test 注释使用 expected 属性,则将 Statement 包装在另一个处理预期异常的语句中
  4. 如果方法的 @Test 批注使用 timeout 属性,请将 Statement 换成另一个处理超时的语句
  5. 用其他语句包装 Statement,这些语句将调用用 @Before@After
  6. 注释的方法
  7. Statement 与将调用规则的其他语句包装起来

更多详情,请查看the JUnit source

所以传递给你的规则的 Statement 包装了一个已经构造的 class (它必须,所以你的规则的 apply() 方法被调用)。

不幸的是,这意味着 Rule 不应多次用于 运行 测试方法,因为测试(或其规则)可能具有上次设置的状态方法是 运行.

如果您不能依赖 JUnit 4.12,您可以通过使用 RuleChain 规则来破解它以确保您使用的自定义规则多次 运行 测试运行s "around"其他规则。

如果您使用的是 Java 8,那么您可以将 ExpectedException 规则替换为 Fishbowl 库。

@Test
public void fooTest() {
  Throwable exception = exceptionThrownBy(
    () -> someTestWhichThrowExceptionOfB());
  assertEquals(A.class, exception.getClass());
  assertEquals("B", exception.getMessage());
}

这也可以通过 Java 6 和 7 中的匿名 类 来实现。

@Test
public void fooTest() {
  Throwable exception = exceptionThrownBy(new Statement() {
    public void evaluate() throws Throwable {
      someTestWhichThrowExceptionOfB();
    }
  });
  assertEquals(A.class, exception.getClass());
  assertEquals("B", exception.getMessage());
}