1package org.junit.rules;
2
3import static org.hamcrest.CoreMatchers.instanceOf;
4import static org.junit.matchers.JUnitMatchers.both;
5import static org.junit.matchers.JUnitMatchers.containsString;
6import org.hamcrest.Description;
7import org.hamcrest.Matcher;
8import org.hamcrest.StringDescription;
9import org.junit.Assert;
10import org.junit.internal.matchers.TypeSafeMatcher;
11import org.junit.runners.model.Statement;
12
13/**
14 * The ExpectedException Rule allows in-test specification of expected exception
15 * types and messages:
16 *
17 * <pre>
18 * // These tests all pass.
19 * public static class HasExpectedException {
20 * 	&#064;Rule
21 * 	public ExpectedException thrown= ExpectedException.none();
22 *
23 * 	&#064;Test
24 * 	public void throwsNothing() {
25 *    // no exception expected, none thrown: passes.
26 * 	}
27 *
28 * 	&#064;Test
29 * 	public void throwsNullPointerException() {
30 * 		thrown.expect(NullPointerException.class);
31 * 		throw new NullPointerException();
32 * 	}
33 *
34 * 	&#064;Test
35 * 	public void throwsNullPointerExceptionWithMessage() {
36 * 		thrown.expect(NullPointerException.class);
37 * 		thrown.expectMessage(&quot;happened?&quot;);
38 * 		thrown.expectMessage(startsWith(&quot;What&quot;));
39 * 		throw new NullPointerException(&quot;What happened?&quot;);
40 * 	}
41 * }
42 * </pre>
43 */
44public class ExpectedException implements TestRule {
45	/**
46	 * @return a Rule that expects no exception to be thrown
47	 * (identical to behavior without this Rule)
48	 */
49	public static ExpectedException none() {
50		return new ExpectedException();
51	}
52
53	private Matcher<Object> fMatcher= null;
54
55	private ExpectedException() {
56
57	}
58
59	public Statement apply(Statement base,
60			org.junit.runner.Description description) {
61		return new ExpectedExceptionStatement(base);
62	}
63
64	/**
65	 * Adds {@code matcher} to the list of requirements for any thrown exception.
66	 */
67	// Should be able to remove this suppression in some brave new hamcrest world.
68	@SuppressWarnings("unchecked")
69	public void expect(Matcher<?> matcher) {
70		if (fMatcher == null)
71			fMatcher= (Matcher<Object>) matcher;
72		else
73			fMatcher= both(fMatcher).and(matcher);
74	}
75
76	/**
77	 * Adds to the list of requirements for any thrown exception that it
78	 * should be an instance of {@code type}
79	 */
80	public void expect(Class<? extends Throwable> type) {
81		expect(instanceOf(type));
82	}
83
84	/**
85	 * Adds to the list of requirements for any thrown exception that it
86	 * should <em>contain</em> string {@code substring}
87	 */
88	public void expectMessage(String substring) {
89		expectMessage(containsString(substring));
90	}
91
92	/**
93	 * Adds {@code matcher} to the list of requirements for the message
94	 * returned from any thrown exception.
95	 */
96	public void expectMessage(Matcher<String> matcher) {
97		expect(hasMessage(matcher));
98	}
99
100	private class ExpectedExceptionStatement extends Statement {
101		private final Statement fNext;
102
103		public ExpectedExceptionStatement(Statement base) {
104			fNext= base;
105		}
106
107		@Override
108		public void evaluate() throws Throwable {
109			try {
110				fNext.evaluate();
111			} catch (Throwable e) {
112				if (fMatcher == null)
113					throw e;
114				Assert.assertThat(e, fMatcher);
115				return;
116			}
117			if (fMatcher != null)
118				throw new AssertionError("Expected test to throw "
119						+ StringDescription.toString(fMatcher));
120		}
121	}
122
123	private Matcher<Throwable> hasMessage(final Matcher<String> matcher) {
124		return new TypeSafeMatcher<Throwable>() {
125			public void describeTo(Description description) {
126				description.appendText("exception with message ");
127				description.appendDescriptionOf(matcher);
128			}
129
130			@Override
131			public boolean matchesSafely(Throwable item) {
132				return matcher.matches(item.getMessage());
133			}
134		};
135	}
136}
137