1 /*
2  * Copyright (c) 2017 Mockito contributors
3  * This program is made available under the terms of the MIT License.
4  */
5 package org.mockitoutil;
6 
7 import org.assertj.core.api.AbstractThrowableAssert;
8 import org.assertj.core.api.Assertions;
9 import org.junit.rules.MethodRule;
10 import org.junit.runners.model.FrameworkMethod;
11 import org.junit.runners.model.Statement;
12 
13 /**
14  * Junit rule for testing exception handling other JUnit rules, like Mockito JUnit rules.
15  * Makes it easy to assert on expected exceptions triggered by the rule under test.
16  */
17 public class SafeJUnitRule implements MethodRule {
18 
19     private final MethodRule testedRule;
20     private FailureAssert failureAssert = null;
21     private Runnable successAssert = new Runnable() { public void run() { } };
22 
23     /**
24      * Wraps rule under test with exception handling so that it is easy to assert on exceptions fired from the tested rule.
25      */
SafeJUnitRule(MethodRule testedRule)26     public SafeJUnitRule(MethodRule testedRule) {
27         this.testedRule = testedRule;
28     }
29 
apply(final Statement base, final FrameworkMethod method, final Object target)30     public Statement apply(final Statement base, final FrameworkMethod method, final Object target) {
31         return new Statement() {
32             public void evaluate() throws Throwable {
33                 try {
34                     testedRule.apply(base, method, target).evaluate();
35                     successAssert.run();
36                 } catch (Throwable t) {
37                     if (failureAssert == null) {
38                         throw t;
39                     }
40                     failureAssert.doAssert(t);
41                     return;
42                 }
43                 if (failureAssert != null) {
44                     //looks like the user expects a throwable but it was not thrown!
45                     throw new ExpectedThrowableNotReported("Expected the tested rule to throw an exception but it did not.");
46                 }
47             }
48         };
49     }
50 
51     /**
52      * Expects that _after_ the test, the rule will fire specific exception with specific exception message
53      */
54     public void expectFailure(final Class<? extends Throwable> expected, final String expectedMessage) {
55         this.expectFailure(new FailureAssert() {
56             public void doAssert(Throwable t) {
57                 assertThrowable(t, expected).hasMessage(expectedMessage);
58             }
59         });
60     }
61 
62     /**
63      * Expects that _after_ the test, the rule will fire specific exception with specific exception message
64      */
65     public void expectFailure(final Class<? extends Throwable> expected) {
66         this.expectFailure(new FailureAssert() {
67             public void doAssert(Throwable t) {
68                 assertThrowable(t, expected);
69             }
70         });
71     }
72 
73     private static AbstractThrowableAssert assertThrowable(Throwable throwable, Class<? extends Throwable> expected) {
74         return Assertions.assertThat(throwable).isInstanceOf(expected);
75     }
76 
77     /**
78      * Expects that _after_ the test, the rule will fire an exception
79      */
80     public void expectFailure(FailureAssert failureAssert) {
81         this.failureAssert = failureAssert;
82     }
83 
84     /**
85      * Expects that _after_ the test, given assert will run
86      */
87     public void expectSuccess(Runnable successAssert) {
88         this.successAssert = successAssert;
89     }
90 
91     /**
92      * Offers a way to assert the throwable triggered by the tested rule
93      */
94     public interface FailureAssert {
95         void doAssert(Throwable t);
96     }
97 
98     /**
99      * Thrown when user expects the tested rule to throw an exception but no exception was thrown
100      */
101     class ExpectedThrowableNotReported extends Throwable {
102         public ExpectedThrowableNotReported(String message) {
103             super(message);
104         }
105     }
106 }
107