Skip to content

Commit 36ebbba

Browse files
motlintimtebeek
andauthored
Preserve throws clause when checked exceptions exist outside assertThrows. (#899)
* Preserve throws clause when checked exceptions exist outside assertThrows. * Use `reduce` & condense --------- Co-authored-by: Tim te Beek <tim@moderne.io>
1 parent 58d2238 commit 36ebbba

3 files changed

Lines changed: 134 additions & 2 deletions

File tree

src/main/java/org/openrewrite/java/testing/assertj/JUnitTryFailToAssertThatThrownBy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public J visitTry(J.Try tryBlock, ExecutionContext ctx) {
111111
return JavaTemplate.builder(template)
112112
.contextSensitive()
113113
.staticImports("org.assertj.core.api.Assertions.assertThatThrownBy")
114-
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3"))
114+
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-jupiter-api-5", "assertj-core-3"))
115115
.build()
116116
.<J.MethodInvocation>apply(getCursor(), try_.getCoordinates().replace(), lambdaStatements.toArray());
117117
}

src/main/java/org/openrewrite/java/testing/junit5/ExpectedExceptionToAssertThrows.java

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
6363
private static class ExpectedExceptionToAssertThrowsVisitor extends JavaIsoVisitor<ExecutionContext> {
6464

6565
private static final String FIRST_EXPECTED_EXCEPTION_METHOD_INVOCATION = "firstExpectedExceptionMethodInvocation";
66+
private static final String STATEMENTS_BEFORE_EXPECT_EXCEPTION = "statementsBeforeExpectException";
6667
private static final String STATEMENTS_AFTER_EXPECT_EXCEPTION = "statementsAfterExpectException";
6768
private static final String HAS_MATCHER = "hasMatcher";
6869
private static final String EXCEPTION_CLASS = "exceptionClass";
@@ -100,13 +101,60 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex
100101
if (getCursor().pollMessage("hasExpectException") != null) {
101102
List<NameTree> thrown = m.getThrows();
102103
if (thrown != null && !thrown.isEmpty()) {
104+
List<Statement> statementsBeforeExpect = getCursor().pollMessage(STATEMENTS_BEFORE_EXPECT_EXCEPTION);
105+
if (statementsBeforeExpect != null && statementsBeforeExpect.stream().anyMatch(this::statementThrowsCheckedException)) {
106+
return m;
107+
}
103108
assert m.getBody() != null;
104109
return m.withBody(m.getBody().withPrefix(thrown.get(0).getPrefix())).withThrows(emptyList());
105110
}
106111
}
107112
return m;
108113
}
109114

115+
private boolean statementThrowsCheckedException(Statement statement) {
116+
return new JavaIsoVisitor<AtomicBoolean>() {
117+
@Override
118+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, AtomicBoolean found) {
119+
if (found.get()) {
120+
return method;
121+
}
122+
JavaType.Method methodType = method.getMethodType();
123+
if (methodType != null) {
124+
for (JavaType thrownException : methodType.getThrownExceptions()) {
125+
if (isCheckedException(thrownException)) {
126+
found.set(true);
127+
return method;
128+
}
129+
}
130+
}
131+
return super.visitMethodInvocation(method, found);
132+
}
133+
134+
@Override
135+
public J.NewClass visitNewClass(J.NewClass newClass, AtomicBoolean found) {
136+
if (found.get()) {
137+
return newClass;
138+
}
139+
JavaType.Method constructorType = newClass.getConstructorType();
140+
if (constructorType != null) {
141+
for (JavaType thrownException : constructorType.getThrownExceptions()) {
142+
if (isCheckedException(thrownException)) {
143+
found.set(true);
144+
return newClass;
145+
}
146+
}
147+
}
148+
return super.visitNewClass(newClass, found);
149+
}
150+
}.reduce(statement, new AtomicBoolean(false)).get();
151+
}
152+
153+
private boolean isCheckedException(JavaType exceptionType) {
154+
return !TypeUtils.isAssignableTo("java.lang.RuntimeException", exceptionType) &&
155+
!TypeUtils.isAssignableTo("java.lang.Error", exceptionType);
156+
}
157+
110158
@Override
111159
public J.Block visitBlock(J.Block block, ExecutionContext ctx) {
112160
J.Block b = super.visitBlock(block, ctx);
@@ -175,7 +223,13 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu
175223
return method;
176224
}
177225
getCursor().dropParentUntil(J.MethodDeclaration.class::isInstance).putMessage("hasExpectException", true);
178-
getCursor().dropParentUntil(J.Block.class::isInstance).computeMessageIfAbsent(FIRST_EXPECTED_EXCEPTION_METHOD_INVOCATION, k -> method);
226+
Cursor blockCursor = getCursor().dropParentUntil(J.Block.class::isInstance);
227+
blockCursor.computeMessageIfAbsent(FIRST_EXPECTED_EXCEPTION_METHOD_INVOCATION, k -> method);
228+
229+
List<Statement> predecessorStatements = findPredecessorStatements(getCursor());
230+
getCursor().dropParentUntil(J.MethodDeclaration.class::isInstance)
231+
.computeMessageIfAbsent(STATEMENTS_BEFORE_EXPECT_EXCEPTION, k -> predecessorStatements);
232+
179233
List<Statement> successorStatements = findSuccessorStatements(getCursor());
180234
getCursor().putMessageOnFirstEnclosing(J.Block.class, STATEMENTS_AFTER_EXPECT_EXCEPTION, successorStatements);
181235
if (EXPECTED_EXCEPTION_CLASS_MATCHER.matches(method)) {
@@ -186,6 +240,25 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu
186240
return method;
187241
}
188242

243+
/**
244+
* From the current cursor point find all preceding statements in the method body.
245+
*/
246+
private List<Statement> findPredecessorStatements(Cursor cursor) {
247+
J.MethodDeclaration methodDecl = cursor.firstEnclosing(J.MethodDeclaration.class);
248+
if (methodDecl == null || methodDecl.getBody() == null) {
249+
return emptyList();
250+
}
251+
List<Statement> predecessorStatements = new ArrayList<>();
252+
Statement currentStatement = cursor.firstEnclosing(Statement.class);
253+
for (Statement statement : methodDecl.getBody().getStatements()) {
254+
if (statement == currentStatement) {
255+
break;
256+
}
257+
predecessorStatements.add(statement);
258+
}
259+
return predecessorStatements;
260+
}
261+
189262
/**
190263
* From the current cursor point find all the next statements that can be executed in the current path.
191264
*/

src/test/java/org/openrewrite/java/testing/junit5/ExpectedExceptionToAssertThrowsTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,65 @@ public void expectExceptionUseCases() {
424424
);
425425
}
426426

427+
@Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/55")
428+
@Test
429+
void preserveThrowsWhenCodeBeforeExpectThrowsCheckedException() {
430+
//language=java
431+
rewriteRun(
432+
java(
433+
"""
434+
import org.junit.Rule;
435+
import org.junit.Test;
436+
import org.junit.rules.ExpectedException;
437+
438+
class MyTest {
439+
440+
@Rule
441+
ExpectedException thrown = ExpectedException.none();
442+
443+
@Test
444+
public void testMethod() throws InterruptedException {
445+
setup();
446+
this.thrown.expect(IllegalArgumentException.class);
447+
doSomething();
448+
}
449+
450+
void setup() throws InterruptedException {
451+
Thread.sleep(100);
452+
}
453+
454+
void doSomething() {
455+
throw new IllegalArgumentException();
456+
}
457+
}
458+
""",
459+
"""
460+
import org.junit.Test;
461+
462+
import static org.junit.jupiter.api.Assertions.assertThrows;
463+
464+
class MyTest {
465+
466+
@Test
467+
public void testMethod() throws InterruptedException {
468+
setup();
469+
assertThrows(IllegalArgumentException.class, () ->
470+
doSomething());
471+
}
472+
473+
void setup() throws InterruptedException {
474+
Thread.sleep(100);
475+
}
476+
477+
void doSomething() {
478+
throw new IllegalArgumentException();
479+
}
480+
}
481+
"""
482+
)
483+
);
484+
}
485+
427486
@Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/563")
428487
@Test
429488
void expectedCheckedExceptionThrowsRemoved() {

0 commit comments

Comments
 (0)