Skip to content

Commit c270c74

Browse files
authored
Add .contextSensitive() to Hamcrest conversion templates and improve type matching in CollapseConsecutiveAssertThatStatements (#912)
* Add .contextSensitive() to Hamcrest conversion templates and improve type matching in CollapseConsecutiveAssertThatStatements - Add .contextSensitive() to JavaTemplate builders in HamcrestMatcherToAssertJ, HamcrestNotMatcherToAssertJ, and HamcrestIsMatcherToAssertJ to ensure templates are compiled in proper source context - Improve CollapseConsecutiveAssertThatStatements with fallback for partially resolved SELF return types (e.g., when assertion methods return AbstractIterableAssert instead of ListAssert) - Update test expectations: isNotNull() now collapses with other assertions and is removed by SimplifyRedundantAssertJChains - Add test case for custom types to verify hamcrest-to-AssertJ conversion works Fixes the issue where the AssertJ best practices recipe required multiple runs on Hamcrest test files before seeing chained assertions. * Fix typeToIndicator to include erased type for java types Use #{any(java.util.List)} instead of #{any()} for Java standard library types in HamcrestMatcherToAssertJ.typeToIndicator(). This gives the template parser enough type information to resolve the correct assertThat overload, producing proper return types even when generic type parameters reference custom types not on the template parser classpath. This enables CollapseConsecutiveAssertThatStatements to chain assertions on custom types (like List<Biscuit>) in a single pass, no longer requiring TypeValidation.none() in tests.
1 parent 00bc57f commit c270c74

File tree

5 files changed

+68
-2
lines changed

5 files changed

+68
-2
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,15 @@ private boolean isGroupableAssertion(J.MethodInvocation assertion) {
133133
if (assertThatFq != null && assertionFq != null) {
134134
return TypeUtils.isOfType(assertThatFq.getType(), assertionFq.getType());
135135
}
136+
137+
// When assertion methods return SELF but the type parameter is not fully resolved,
138+
// the return type may be a superclass (e.g. AbstractIterableAssert instead of ListAssert).
139+
// Check if assertThatType is a subtype of assertionType to handle this case.
140+
JavaType.FullyQualified assertThatFqType = TypeUtils.asFullyQualified(assertThatType);
141+
JavaType.FullyQualified assertionFqType = TypeUtils.asFullyQualified(assertionType);
142+
if (assertThatFqType != null && assertionFqType != null) {
143+
return TypeUtils.isAssignableTo(assertionFqType.getFullyQualifiedName(), assertThatType);
144+
}
136145
}
137146
return false;
138147
}

src/main/java/org/openrewrite/java/testing/hamcrest/HamcrestIsMatcherToAssertJ.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu
8585
new Object[]{actualArgument, expectedArgument};
8686

8787
return JavaTemplate.builder(template)
88+
.contextSensitive()
8889
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3"))
8990
.staticImports("org.assertj.core.api.Assertions.assertThat")
9091
.build()

src/main/java/org/openrewrite/java/testing/hamcrest/HamcrestMatcherToAssertJ.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ private J.MethodInvocation replace(J.MethodInvocation mi, ExecutionContext ctx)
106106
(reasonArgument != null ? ".as(#{any(String)})" : "") +
107107
".%s(%s)",
108108
actual, assertion, argsTemplate))
109+
.contextSensitive()
109110
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3"))
110111
.staticImports(
111112
"org.assertj.core.api.Assertions.assertThat",
@@ -186,9 +187,12 @@ private String typeToIndicator(@Nullable JavaType type) {
186187
type.toString().replaceAll("<.*>", "") : "java.lang.Object";
187188
return String.format("#{anyArray(%s)}", str);
188189
}
189-
if (type instanceof JavaType.Primitive || type != null && type.toString().startsWith("java.")) {
190+
if (type instanceof JavaType.Primitive) {
190191
return "#{any()}";
191192
}
193+
if (type != null && type.toString().startsWith("java.")) {
194+
return String.format("#{any(%s)}", type.toString().replaceAll("<.*>", ""));
195+
}
192196
return "#{any(java.lang.Object)}";
193197
}
194198
}

src/main/java/org/openrewrite/java/testing/hamcrest/HamcrestNotMatcherToAssertJ.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ private J.MethodInvocation handleTwoArgumentCase(J.MethodInvocation mi, Expressi
103103
.collect(joining(", "));
104104
JavaTemplate template = JavaTemplate.builder(String.format("assertThat(%s).%s(%s)",
105105
actual, assertion, argumentsTemplate))
106+
.contextSensitive()
106107
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3"))
107108
.staticImports("org.assertj.core.api.Assertions.assertThat")
108109
.build();
@@ -135,6 +136,7 @@ private J.MethodInvocation handleThreeArgumentCase(J.MethodInvocation mi, Expres
135136
.collect(joining(", "));
136137
JavaTemplate template = JavaTemplate.builder(String.format("assertThat(%s).as(#{any(String)}).%s(%s)",
137138
actual, assertion, argumentsTemplate))
139+
.contextSensitive()
138140
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3"))
139141
.staticImports("org.assertj.core.api.Assertions.assertThat")
140142
.build();

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

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ void biscuits() {
132132
class BiscuitTest {
133133
void biscuits() {
134134
List<String> biscuits = List.of("Ginger", "Chocolate", "Oatmeal");
135-
assertThat(biscuits).isNotNull();
136135
assertThat(biscuits)
137136
.hasSize(3)
138137
.contains("Chocolate");
@@ -143,6 +142,57 @@ void biscuits() {
143142
);
144143
}
145144

145+
@Test
146+
void hamcrestToCollapsedAssertionsWithCustomType() {
147+
rewriteRun(
148+
spec -> spec.parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "hamcrest-3")),
149+
//language=java
150+
java(
151+
"""
152+
class Biscuit {
153+
String name;
154+
Biscuit(String name) {
155+
this.name = name;
156+
}
157+
}
158+
"""
159+
),
160+
//language=java
161+
java(
162+
"""
163+
import java.util.List;
164+
165+
import static org.hamcrest.MatcherAssert.assertThat;
166+
import static org.hamcrest.Matchers.*;
167+
168+
class BiscuitTest {
169+
void biscuits() {
170+
List<Biscuit> biscuits = List.of(new Biscuit("Ginger"), new Biscuit("Chocolate"), new Biscuit("Oatmeal"));
171+
assertThat(biscuits, hasSize(3));
172+
assertThat(biscuits, hasItem(new Biscuit("Chocolate")));
173+
assertThat(biscuits, not(hasItem(new Biscuit("Raisin"))));
174+
}
175+
}
176+
""",
177+
"""
178+
import java.util.List;
179+
180+
import static org.assertj.core.api.Assertions.assertThat;
181+
182+
class BiscuitTest {
183+
void biscuits() {
184+
List<Biscuit> biscuits = List.of(new Biscuit("Ginger"), new Biscuit("Chocolate"), new Biscuit("Oatmeal"));
185+
assertThat(biscuits)
186+
.hasSize(3)
187+
.contains(new Biscuit("Chocolate"))
188+
.doesNotContain(new Biscuit("Raisin"));
189+
}
190+
}
191+
"""
192+
)
193+
);
194+
}
195+
146196
@Test
147197
void junitCollapsedAssertions() {
148198
rewriteRun(

0 commit comments

Comments
 (0)