diff --git a/src/main/java/org/openrewrite/java/testing/junitassertj/JUnitFailToAssertJFail.java b/src/main/java/org/openrewrite/java/testing/junitassertj/JUnitFailToAssertJFail.java new file mode 100644 index 000000000..c691bb3d0 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/junitassertj/JUnitFailToAssertJFail.java @@ -0,0 +1,118 @@ +/* + * Copyright 2020 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.testing.junitassertj; + +import org.openrewrite.java.AutoFormat; +import org.openrewrite.java.JavaIsoRefactorVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.RemoveUnusedImports; +import org.openrewrite.java.tree.*; + +import java.util.ArrayList; +import java.util.List; + +import static org.openrewrite.Formatting.EMPTY; +import static org.openrewrite.Formatting.format; +import static org.openrewrite.Tree.randomId; +import static org.openrewrite.java.tree.MethodTypeBuilder.newMethodType; + +/** + * This is a refactoring visitor that will convert JUnit-style fail() to assertJ's fail(). + * + * This visitor only supports the migration of the following JUnit 5 fail() methods: + * + *

+ *  fail()                                  ->   fail("")
+ *  fail(String message)                    ->   fail(String message)
+ *  fail(String message, Throwable cause)   ->   fail(String message, Throwable cause)
+ *  fail(Throwable cause)                   ->   fail("", Throwable cause)
+ * 
+ * + * Note: There is an additional method signature in JUnit that accepts a StringSupplier as an argument. Attempts + * to map this signature into assertJ's model obfuscates the original assertion. + */ +public class JUnitFailToAssertJFail extends JavaIsoRefactorVisitor { + private static final String JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME = "org.junit.jupiter.api.Assertions"; + private static final String ASSERTJ_QUALIFIED_ASSERTIONS_CLASS_NAME = "org.assertj.core.api.Assertions"; + + /** + * This matcher finds the junit methods that will be migrated by this visitor. + */ + private static final MethodMatcher JUNIT_FAIL_MATCHER = new MethodMatcher( + JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME + " fail(..)" + ); + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method) { + J.MethodInvocation original = method; + if (!JUNIT_FAIL_MATCHER.matches(method)) { + return original; + } + + MethodTypeBuilder methodTypeBuilder = newMethodType() + .declaringClass(ASSERTJ_QUALIFIED_ASSERTIONS_CLASS_NAME) + .flags(Flag.Public, Flag.Static) + .returnType( + JavaType.Class.build("java.lang.Object"), + new JavaType.GenericTypeVariable( + "T", + JavaType.Class.build("java.lang.Object") + ) + ) + .name("fail"); + + List originalArgs = original.getArgs().getArgs(); + List newArgs = new ArrayList<>(); + + if(originalArgs.get(0) instanceof J.Literal) { + newArgs.add(originalArgs.get(0)); + methodTypeBuilder.parameter(JavaType.Class.build("java.lang.String"), "failureMessage"); + + if(originalArgs.size() == 2) { + newArgs.add(originalArgs.get(1)); + } + } else if(originalArgs.get(0) instanceof J.Empty) { + newArgs.add(J.Literal.buildString("")); + } else { + methodTypeBuilder.parameter(JavaType.Class.build("java.lang.String"), "failureMessage"); + methodTypeBuilder.parameter("java.lang.Throwable", "realCause"); + newArgs.add(J.Literal.buildString("")); + newArgs.add(originalArgs.get(0)); + } + + JavaType.Method assertJFailMethodType = methodTypeBuilder.build(); + + J.MethodInvocation assertJFail = new J.MethodInvocation( + randomId(), + null, + null, + J.Ident.build(randomId(), "fail", JavaType.Primitive.Void, EMPTY), + new J.MethodInvocation.Arguments( + randomId(), + newArgs, + EMPTY + ), + assertJFailMethodType, + format("\n") + ); + + maybeAddImport(ASSERTJ_QUALIFIED_ASSERTIONS_CLASS_NAME, "fail"); + andThen(new RemoveUnusedImports()); + andThen(new AutoFormat(assertJFail)); + + return assertJFail; + } +} diff --git a/src/test/kotlin/org/openrewrite/java/testing/junitassertj/JUnitFailToAssertJFailTest.kt b/src/test/kotlin/org/openrewrite/java/testing/junitassertj/JUnitFailToAssertJFailTest.kt new file mode 100644 index 000000000..1bfa598d6 --- /dev/null +++ b/src/test/kotlin/org/openrewrite/java/testing/junitassertj/JUnitFailToAssertJFailTest.kt @@ -0,0 +1,227 @@ +/* + * Copyright 2020 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.testing.junitassertj + +import org.junit.jupiter.api.Test +import org.openrewrite.Parser +import org.openrewrite.RefactorVisitor +import org.openrewrite.RefactorVisitorTestForParser +import org.openrewrite.java.JavaParser +import org.openrewrite.java.tree.J + +class JUnitFailToAssertJFailTest : RefactorVisitorTestForParser { + override val parser: Parser = JavaParser.fromJavaVersion() + .classpath("junit", "assertj-core", "apiguardian-api") + .build() + + override val visitors: Iterable> = listOf(JUnitFailToAssertJFail()) + + @Test + fun singleStaticMethodNoMessage() = assertRefactored( + before = """ + import org.junit.Test; + + import static org.junit.jupiter.api.Assertions.fail; + + public class A { + + @Test + public void test() { + fail(); + } + } + """, + after = """ + import org.junit.Test; + + import static org.assertj.core.api.Assertions.fail; + + public class A { + + @Test + public void test() { + fail(""); + } + } + """ + ) + + @Test + fun singleStaticMethodWithMessage() = assertRefactored( + before = """ + import org.junit.Test; + + import static org.junit.jupiter.api.Assertions.fail; + + public class A { + + @Test + public void test() { + fail("This should fail"); + } + } + """, + after = """ + import org.junit.Test; + + import static org.assertj.core.api.Assertions.fail; + + public class A { + + @Test + public void test() { + fail("This should fail"); + } + } + """ + ) + + @Test + fun singleStaticMethodWithMessageAndCause() = assertRefactored( + before = """ + import org.junit.Test; + + import static org.junit.jupiter.api.Assertions.fail; + + public class A { + + @Test + public void test() { + Throwable t = new Throwable(); + fail("This should fail", t); + } + } + """, + after = """ + import org.junit.Test; + + import static org.assertj.core.api.Assertions.fail; + + public class A { + + @Test + public void test() { + Throwable t = new Throwable(); + fail("This should fail", t); + } + } + """ + ) + + @Test + fun singleStaticMethodWithCause() = assertRefactored( + before = """ + import org.junit.Test; + + import static org.junit.jupiter.api.Assertions.fail; + + public class A { + + @Test + public void test() { + Throwable t = new Throwable(); + fail(t); + fail(new Throwable()); + } + } + """, + after = """ + import org.junit.Test; + + import static org.assertj.core.api.Assertions.fail; + + public class A { + + @Test + public void test() { + Throwable t = new Throwable(); + fail("",t); + fail("",new Throwable()); + } + } + """ + ) + + @Test + fun inlineReference() = assertRefactored( + before = """ + import org.junit.Test; + + public class A { + + @Test + public void test() { + org.junit.jupiter.api.Assertions.fail(); + org.junit.jupiter.api.Assertions.fail("This should fail"); + org.junit.jupiter.api.Assertions.fail("This should fail", new Throwable()); + org.junit.jupiter.api.Assertions.fail(new Throwable()); + } + } + """, + after = """ + import org.junit.Test; + + import static org.assertj.core.api.Assertions.fail; + + public class A { + + @Test + public void test() { + fail(""); + fail("This should fail"); + fail("This should fail", new Throwable()); + fail("",new Throwable()); + } + } + """ + ) + + @Test + fun mixedReferences() = assertRefactored( + before = """ + import org.junit.Test; + + import static org.junit.jupiter.api.Assertions.fail; + + public class A { + + @Test + public void test() { + fail(); + org.junit.jupiter.api.Assertions.fail("This should fail"); + fail("This should fail", new Throwable()); + org.junit.jupiter.api.Assertions.fail(new Throwable()); + } + } + """, + after = """ + import org.junit.Test; + + import static org.assertj.core.api.Assertions.fail; + + public class A { + + @Test + public void test() { + fail(""); + fail("This should fail"); + fail("This should fail", new Throwable()); + fail("",new Throwable()); + } + } + """ + ) +}