GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

handleTestExecutionException(ExtensionContext,Throwable)   A
last analyzed

Complexity

Conditions 4

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
c 0
b 0
f 0
dl 0
loc 19
rs 9.8
eloc 12
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package io.github.glytching.junit.extension.exception;
18
19
import org.junit.jupiter.api.Assertions;
20
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
21
import org.junit.jupiter.api.extension.ExtensionContext;
22
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
23
24
import java.util.Optional;
25
import java.util.function.Function;
26
import java.util.function.Predicate;
27
28
import static io.github.glytching.junit.extension.util.ExtensionUtil.getStore;
29
import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation;
30
import static org.junit.platform.commons.util.FunctionUtils.where;
31
32
/**
33
 * The expected exception extension allows the developer to express the expectations of an exception
34
 * to be thrown by the code-under-test. This extension is engaged by adding the {@link
35
 * ExpectedException} annotation to a test method.
36
 *
37
 * <p>Usage example:
38
 *
39
 * <pre>
40
 * public class MyTest {
41
 *
42
 *     &#064;Test
43
 *     &#064;ExpectedException(type = Exception.class, messageIs = "Boom!")
44
 *     public void canHandleAnException() throws Exception {
45
 *         // ...
46
 *         throw new Exception("Boom!");
47
 *     }
48
 *
49
 *
50
 *     &#064;Test
51
 *     &#064;ExpectedException(type = RuntimeException.class, messageStartsWith = "Bye")
52
 *     public void canHandleAnExceptionWithAMessageWhichStartsWith() {
53
 *         // ...
54
 *         throw new RuntimeException("Bye bye");
55
 *     }
56
 * }
57
 * </pre>
58
 *
59
 * Notes:
60
 *
61
 * <ul>
62
 *   <li>Since usage of this extension implies that the developer <i>expects</i> an exception to be
63
 *       thrown the following test case will fail since it throws no exception:
64
 *       <pre>
65
 *   &#064;Test
66
 *   &#064;ExpectedException(type = Throwable.class)
67
 *   public void failsTestForMissingException() {}
68
 * </pre>
69
 *       This is to avoid a false positive where a test is declared to expect an exception and
70
 *       passes even if no exception is thrown.
71
 *   <li>The expected exception type will match on the given type and any subclasses of that type.
72
 *       In other words, the following test will pass:
73
 *       <pre>
74
 *          &#064;Test
75
 *          &#064;ExpectedException(type = Throwable.class, messageIs = "Boom!")
76
 *          public void canHandleAThrowable() throws Throwable {
77
 *              throw new Exception("Boom!");
78
 *          }
79
 *       </pre>
80
 *       This is for consistency with JUnit Jupiter, in which <code>AssertThrows</code> matches an
81
 *       exception type or any subclass of that exception type.
82
 * </ul>
83
 *
84
 * @see <a href="https://github.com/junit-team/junit4/wiki/Rules#expectedexception-rules">JUnit 4
85
 *     ExpectedException Rule</a>
86
 * @since 1.0.0
87
 */
88
public class ExpectedExceptionExtension
89
    implements TestExecutionExceptionHandler, AfterTestExecutionCallback {
90
91
  private static final String KEY = "exceptionWasHandled";
92
93
  private final Function<Throwable, String> function;
94
95
  public ExpectedExceptionExtension() {
96
    this.function = Throwable::getMessage;
97
  }
98
99
  /**
100
   * Handle the supplied {@code Throwable throwable}. If the {@code extensionContext} is annotated
101
   * with {@link ExpectedException} and if the {@code throwable} matches the expectations expressed
102
   * in the {@link ExpectedException} annotation then the supplied {@code throwable} is swallowed
103
   * otherwise the supplied {@code throwable} is rethrown.
104
   *
105
   * @param extensionContext the current extension context
106
   * @param throwable the {@code Throwable} to handle
107
   * @throws Throwable
108
   */
109
  @Override
110
  public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable)
111
      throws Throwable {
112
    Optional<ExpectedException> optional =
113
        findAnnotation(extensionContext.getTestMethod(), ExpectedException.class);
114
    if (optional.isPresent()) {
115
116
      ExpectedException annotation = optional.get();
117
      // see https://github.com/glytching/junit-extensions/issues/5
118
      if (annotation.type().isAssignableFrom(throwable.getClass())) {
119
        if (where(function, getPredicate(annotation)).test(throwable)) {
120
          getStore(extensionContext, this.getClass()).put(KEY, true);
121
122
          // swallow the exception because the caller has declared it to be expected
123
          return;
124
        }
125
      }
126
    }
127
    throw throwable;
128
  }
129
130
  /**
131
   * The presence of {@link ExpectedException} on a test is a clear statement by the developer that
132
   * some exception must be thrown by that test. Therefore, if the invocation arrives here without
133
   * having been evaluated by {@link #handleTestExecutionException(ExtensionContext, Throwable)} and
134
   * with no exception in the context then this expectation has not been met and rather than failing
135
   * silently (and giving a false positive) the library will explicitly fail on this condition.
136
   *
137
   * @param extensionContext
138
   * @throws Exception
139
   */
140
  @Override
141
  public void afterTestExecution(ExtensionContext extensionContext) throws Exception {
142
    Boolean exceptionWasHandled =
143
        (Boolean) getStore(extensionContext, this.getClass()).getOrComputeIfAbsent(KEY, s -> false);
144
    if (!exceptionWasHandled && !extensionContext.getExecutionException().isPresent()) {
145
      Assertions.fail("Expected an exception but no exception was thrown!");
146
    }
147
  }
148
149
  /**
150
   * Maps the expectations expressed in the given {@code annotation} to a {@link Predicate}.
151
   *
152
   * @param annotation encapsulates the callers' expression of what's to be deemed an acceptable
153
   *     exception
154
   * @return a predicate which can be used to assess whether an exception matches the expectations
155
   *     expressed in the given {@code annotation}
156
   */
157
  private Predicate<String> getPredicate(ExpectedException annotation) {
158
    if (has(annotation.messageStartsWith())) {
159
      return s -> s.startsWith(annotation.messageStartsWith());
160
    } else if (has(annotation.messageContains())) {
161
      return s -> s.contains(annotation.messageContains());
162
    } else if (has(annotation.messageIs())) {
163
      return s -> s.equals(annotation.messageIs());
164
    } else {
165
      // the default
166
      return s -> true;
167
    }
168
  }
169
170
  private boolean has(String incoming) {
171
    return incoming != null && incoming.length() > 0;
172
  }
173
}
174