Q:

Is It Possible to Override an Expected Exception in JUnit?

I recently found out that JUnit > 4.10 allows the usage of @Rule and ExpectedException. Since I'm not big on duplicating code I tried the following. For a better understanding I scaled it down from several tests to just these two. The MockitoJUnitRunner is intentional although it's not used in the small scaled example.
pom.xml
<dependencies>
  <!-- Test dependencies -->
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.10.19</version>
    <scope>test</scope>
  </dependency>
</dependencies>

TestBase
 @RunWith(MockitoJUnitRunner.class)
 public class TestBase {
   /** JUnit > 4.10 allows expected exception handling like this */
   @Rule
   public ExpectedException exception = ExpectedException.none();

   @Before
   public void setup() {
     this.expectBadParam();
   }

   protected void expectBadParam() {
     this.exception.expect(NullPointerException.class);
   }
 }

The problem is that the following test is not working as I would expect it to. What I'm trying is by default expect an exception type and in some cases run a normal JUnit test. I can't reset the expected exception once it's set.
 public class ExpectedExceptionTest extends TestBase {
   @Test
   public void error() {
     throw new NullPointerException();
   }

   @Test
   public void success() {
     this.exception = ExpectedException.none();
     // this should be a success
   }
 }

I already found a different solution by duplicating the expectBadParam method in each method I expect an exception as well as overriding the @Before annotation in the test class. However I'm hoping someone can help me understand why this is not working?

A:

The reason it's not working as you expect (no pun intended) is related to how TestRules work with JUnit. 
Effectively what happens is that the test framework inspects the test case for TestRule instances, and then calls the TestRule.apply() method on each on in turn. This method takes a Statement object and returns a Statement(). Your test case object is initially wrapped in a Statement, given to the first TestRule, which returns a brand new Statement wrapping the original Statement. So, basically, the TestRule is being given the opportunity to adapt the original TestCase, generally adding new functionality. Once the framework has gone through all the TestRule instances it calls the Statement.evaluate() method, like it would do for any 'standard' test case it builds up which doesn't have any TestRules attached to it.
The key thing here is that all the interaction between the framework and the TestRule instances happens at test case construction time. Once the test case has been built up, the fields containing the rules are no longer queried or directly interacted with by the test framework. Their main purpose afterwards is for tests to interact with the mutable state contained within the rules. So, if you change the instance field as you do in your test case success() you'll have absolutely no effect on the outcome of the rules, because the rule expecting an IllegalArgumentException has already been applied to the test case.
There's a 'typical' shape to a TestRule implementation. They look like this...
public void apply(Statement base, Description description) {
    return new Statement() {
        public void evaluate( ) {
            // some initialisation
            try {
                base.evaluate();
            } finally {
                // some tidy up here
            }
        }
    }
}

Here the TestRule gets an opportunity to run some code after the test case has completed. This is how ExpectedException works (although it has a 'catch Exception(e)' block also). During the course of the test you can call methods on the rule instance which builds up state within the TestRule object which is then used when the finally block is called. So, when you call 'exception.expect(IllegalArgumentException.class)`, the test rule stores a matcher in a list and basically matches caught exceptions using that matcher and any others you may have set up. When you reset the instance field in your test case all that state in the original instance is still there and so the test still fails.
To do what you want to do you need a means of resetting the internal state of the ExpectedException instance. Unfortunately, there are no methods on the ExpectedException class which allow you to remove expectations which have been added. It's only really possible to add expectations. And, to be honest, this is for a good reason - your tests should be logically grouped, with finer grained details being added the closer you get to the test case. The act of 'resetting' expectations is an act of removing rather than adding a detail and so suggests that your tests are not logically grouped well enough. It creates maintainability difficulties if some part of a test suite adds some expectations and another part removes some / all of them.
You have 2 options if you want to use ExpectedException here. The first is to split your test class, or test base class, in two. One suite should be for tests which expect the IllegalArgumentException and another for ones which don't or which have some kind of alternative exception they expect. The second is to accept the duplication inherent in having 44 tests which have to explicitly declare they expect an exception and only 4 tests which don't.
It's possible you may be able to achieve what you want with JUnit Theories in some way, although that would depend very much on how your test cases work.

