Skip to content

Commit d08715b

Browse files
authored
Support intermediate methods in SimplifyChainedAssertJAssertion (#812) (#962)
Walk past .as(), .describedAs(), .withFailMessage(), and .overridingErrorMessage() when matching chained assertions, then splice them back into the transformed result. Closes #812
1 parent 03beae5 commit d08715b

2 files changed

Lines changed: 128 additions & 6 deletions

File tree

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

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.openrewrite.java.tree.TypeUtils;
3434

3535
import java.util.ArrayList;
36+
import java.util.Arrays;
3637
import java.util.List;
3738
import java.util.Set;
3839

@@ -85,6 +86,12 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
8586
MethodMatcher assertThatMatcher = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)");
8687
MethodMatcher chainedAssertMatcher = new MethodMatcher("java..* " + chainedAssertion + "(..)");
8788
MethodMatcher assertToReplace = new MethodMatcher("org.assertj.core.api.* " + this.assertToReplace + "(..)");
89+
List<MethodMatcher> intermediateMatchers = Arrays.asList(
90+
new MethodMatcher("org.assertj.core.api.* as(..)"),
91+
new MethodMatcher("org.assertj.core.api.* describedAs(..)"),
92+
new MethodMatcher("org.assertj.core.api.* withFailMessage(..)"),
93+
new MethodMatcher("org.assertj.core.api.* overridingErrorMessage(..)")
94+
);
8895

8996
return new JavaIsoVisitor<ExecutionContext>() {
9097
@Override
@@ -96,9 +103,19 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation methodInvocat
96103
return mi;
97104
}
98105

99-
// assertThat has method call
100-
J.MethodInvocation assertThat = (J.MethodInvocation) mi.getSelect();
101-
if (!assertThatMatcher.matches(assertThat) || !(assertThat.getArguments().get(0) instanceof J.MethodInvocation)) {
106+
// Walk past intermediate methods (as, describedAs, etc.) to find assertThat
107+
List<J.MethodInvocation> intermediates = new ArrayList<>();
108+
J.MethodInvocation current = (J.MethodInvocation) mi.getSelect();
109+
while (!assertThatMatcher.matches(current)) {
110+
if (isIntermediate(current) && current.getSelect() instanceof J.MethodInvocation) {
111+
intermediates.add(current);
112+
current = (J.MethodInvocation) current.getSelect();
113+
} else {
114+
return mi;
115+
}
116+
}
117+
J.MethodInvocation assertThat = current;
118+
if (!(assertThat.getArguments().get(0) instanceof J.MethodInvocation)) {
102119
return mi;
103120
}
104121

@@ -142,11 +159,30 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation methodInvocat
142159
arguments.add(actual);
143160

144161
String template = getStringTemplateAndAppendArguments(assertThatArg, mi, arguments);
145-
return JavaTemplate.builder(String.format(template, dedicatedAssertion))
162+
J.MethodInvocation result = JavaTemplate.builder(String.format(template, dedicatedAssertion))
146163
.contextSensitive()
147164
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-jupiter-api-5", "assertj-core-3"))
148165
.build()
149166
.apply(getCursor(), mi.getCoordinates().replace(), arguments.toArray());
167+
168+
// Splice intermediate methods (as, describedAs, etc.) back into the chain
169+
if (!intermediates.isEmpty()) {
170+
Expression chain = result.getSelect();
171+
for (int i = intermediates.size() - 1; i >= 0; i--) {
172+
chain = intermediates.get(i).withSelect(chain);
173+
}
174+
result = result.withSelect(chain);
175+
}
176+
return result;
177+
}
178+
179+
private boolean isIntermediate(J.MethodInvocation method) {
180+
for (MethodMatcher matcher : intermediateMatchers) {
181+
if (matcher.matches(method)) {
182+
return true;
183+
}
184+
}
185+
return false;
150186
}
151187

152188
private String getStringTemplateAndAppendArguments(J.MethodInvocation assertThatArg, J.MethodInvocation methodToReplace, List<Expression> arguments) {

src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionTest.java

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.openrewrite.java.testing.assertj;
1717

18-
import org.junit.jupiter.api.Disabled;
1918
import org.junit.jupiter.api.Nested;
2019
import org.junit.jupiter.api.Test;
2120
import org.openrewrite.DocumentExample;
@@ -62,7 +61,6 @@ void testMethod() {
6261
);
6362
}
6463

65-
@Disabled(".as(reason) is not yet supported")
6664
@Test
6765
void stringIsEmptyDescribedAs() {
6866
rewriteRun(
@@ -91,6 +89,94 @@ void testMethod(String actual) {
9189
);
9290
}
9391

92+
@Test
93+
void collectionSizeWithAs() {
94+
rewriteRun(
95+
spec -> spec.recipe(new SimplifyChainedAssertJAssertion("size", "isEqualTo", "hasSize", "java.util.Collection")),
96+
//language=java
97+
java(
98+
"""
99+
import java.util.List;
100+
101+
import static org.assertj.core.api.Assertions.assertThat;
102+
103+
class MyTest {
104+
void testMethod(List<String> list) {
105+
assertThat(list.size()).as("Expected size to match").isEqualTo(5);
106+
}
107+
}
108+
""",
109+
"""
110+
import java.util.List;
111+
112+
import static org.assertj.core.api.Assertions.assertThat;
113+
114+
class MyTest {
115+
void testMethod(List<String> list) {
116+
assertThat(list).as("Expected size to match").hasSize(5);
117+
}
118+
}
119+
"""
120+
)
121+
);
122+
}
123+
124+
@Test
125+
void stringIsEmptyWithDescribedAs() {
126+
rewriteRun(
127+
spec -> spec.recipe(new SimplifyChainedAssertJAssertion("isEmpty", "isTrue", "isEmpty", "java.lang.String")),
128+
//language=java
129+
java(
130+
"""
131+
import static org.assertj.core.api.Assertions.assertThat;
132+
133+
class MyTest {
134+
void testMethod(String actual) {
135+
assertThat(actual.isEmpty()).describedAs("Reason").isTrue();
136+
}
137+
}
138+
""",
139+
"""
140+
import static org.assertj.core.api.Assertions.assertThat;
141+
142+
class MyTest {
143+
void testMethod(String actual) {
144+
assertThat(actual).describedAs("Reason").isEmpty();
145+
}
146+
}
147+
"""
148+
)
149+
);
150+
}
151+
152+
@Test
153+
void multipleIntermediates() {
154+
rewriteRun(
155+
spec -> spec.recipe(new SimplifyChainedAssertJAssertion("isEmpty", "isTrue", "isEmpty", "java.lang.String")),
156+
//language=java
157+
java(
158+
"""
159+
import static org.assertj.core.api.Assertions.assertThat;
160+
161+
class MyTest {
162+
void testMethod(String actual) {
163+
assertThat(actual.isEmpty()).as("check").withFailMessage("failed").isTrue();
164+
}
165+
}
166+
""",
167+
"""
168+
import static org.assertj.core.api.Assertions.assertThat;
169+
170+
class MyTest {
171+
void testMethod(String actual) {
172+
assertThat(actual).as("check").withFailMessage("failed").isEmpty();
173+
}
174+
}
175+
"""
176+
)
177+
);
178+
}
179+
94180
@Test
95181
void chainedRecipes() {
96182
rewriteRun(

0 commit comments

Comments
 (0)