0

I created a @Rule similar to @RunWith parameterized.class so that all testcase can repeat tests with different parameters (I cannot use parameterized.class directly becase my test class already has @Runwith for other purpose).

However, the @Rule does not work when testing the following method:

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

In fact, if I hardcode my parameter as a value, the test passes since it does throw the exception of B. But if I set my parameter = MyParameterRule.value(), the test does throw an exception of B as well but then fails and saying it fails because exception of B?

I guess in the second case if I use MyParameterRule, then the exception does not work? then why? how to make it work still?

0

2 Answers 2

3

If you can depend on JUnit 4.12, you may be able to use Parameterized with @UseParametersRunnerFactory (see the Parameterized Javadoc for details).

As for why your parameterized rule wasn't working, here is a (somewhat long) explanation.

JUnit has an internal assumption that a new instance of your test class is created for each test method. JUnit does this so the state stored in an instance of your test from one test method run doesn't affect the next test method run.

The ExpectedException rule has the same expectation. When you call expect, it changes the state of the ExpectedException field that was created at the initialization time of that field. It modifies it to "expect" an exception to be thrown.

When your Rule tries to run the same method twice (with different parameters) that violates this assumption. The first time you call a method that calls expect it will work, but when you call it again, it might not work, because you are re-using the previously-modified ExpectedException rule.

When a JUnit runs a test method for a JUnit4-style test it does the following:

  1. Create an instance of the test class.
  2. Create a Statement that will run the test method
  3. If the method's @Test annotation uses the expected attribute, wrap the Statement with another statement that handles expected exceptions
  4. If the method's @Test annotation uses the timeout attribute, wrap the Statement with another statement that handles timeouts
  5. Wrap that Statement with other statements that will call the methods annotated with @Before and @After
  6. Wrap that Statement with other statements that will call the rules

For more details, look at the JUnit source.

So the Statement that is passed to your rule wraps an already-constructed class (it has to, so the apply() method of your rule is called).

Unfortunately, this means that a Rule should not be used to run a test method multiple times, because the test (or it's rules) could have state that was set the previous time the method was run.

If you can't depend on JUnit 4.12, it's possible you can hack this to work by using the RuleChain Rule to ensure that the custom Rule you use to run the test multiple times runs "around" the other rules.

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks NamshubWriter. what does " find a way to replace your custom runner with a Rule or a base test class", Note my customer rule is already a rule. it is not a runner.
BTW, test fails even if I define one parameter in my parameter list so the problem happens even if the test is not repeated
@user389955 then perhaps just using RuleChain will Help. If not, could you share the code for your custom rule?
@user389955 I meant that if you can't use Parameterized because you need a different custom runner, then you can replace that custom runner with a Rule. But if you use JUnit 4.12, there's a better option. I updated my answer.
NamshubWrite. got. Thanks. 1) that means Rule is not as powerful as I was learned. it does not support multiple Rule in many cases. at least it does not work with Rule ExpectedException. 2) I cannot want to append my new logic to the existing RunWith. 3) I will try RuleChain.
|
0

If you are using Java 8 then you can replace the ExpectedException rule with the Fishbowl library.

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

This can be achieved with anonymous classes in Java 6 and 7, too.

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

1 Comment

I am not using java 8. :-( Thanks for your response

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.