From beb6b19433ac093539d147fb416064d392172d19 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Mon, 2 Nov 2020 13:11:18 -0800 Subject: [PATCH] Fix #5 Handle @RunWith(SpringRunner.class) in junit 4 -> 5 migration This depends on currently unpublished changes in rewrite core libraries --- build.gradle.kts | 1 + .../junit5/SpringRunnerToSpringExtension.java | 138 ++++++++++++++++++ .../SpringRunnerToSpringExtensionTest.kt | 66 +++++++++ 3 files changed, 205 insertions(+) create mode 100755 src/main/java/org/openrewrite/java/testing/junit5/SpringRunnerToSpringExtension.java create mode 100755 src/test/kotlin/org/openrewrite/java/testing/junit5/SpringRunnerToSpringExtensionTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index b1cd31021..38a6e2a20 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -68,6 +68,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:latest.release") testImplementation("org.junit.jupiter:junit-jupiter-params:latest.release") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:latest.release") + testRuntimeOnly("org.springframework:spring-test:latest.release") "afterImplementation"("org.junit.jupiter:junit-jupiter-api:latest.release") "afterImplementation"("org.junit.jupiter:junit-jupiter-params:latest.release") "afterRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:latest.release") diff --git a/src/main/java/org/openrewrite/java/testing/junit5/SpringRunnerToSpringExtension.java b/src/main/java/org/openrewrite/java/testing/junit5/SpringRunnerToSpringExtension.java new file mode 100755 index 000000000..8836b9194 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/junit5/SpringRunnerToSpringExtension.java @@ -0,0 +1,138 @@ +/* + * 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.junit5; + +import org.openrewrite.Formatting; +import org.openrewrite.java.JavaIsoRefactorVisitor; +import org.openrewrite.java.search.SemanticallyEqual; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.openrewrite.Formatting.EMPTY; +import static org.openrewrite.Tree.randomId; + +/** + * JUnit4 Spring test classes are annotated with @RunWith(SpringRunner.class) + * Turn this into the JUnit5-compatible @ExtendsWith(SpringExtension.class) + */ +public class SpringRunnerToSpringExtension extends JavaIsoRefactorVisitor { + private static final JavaType.Class runWithType = JavaType.Class.build("org.junit.runner.RunWith"); + private static final J.Ident runWithIdent = J.Ident.build( + randomId(), + "RunWith", + runWithType, + EMPTY); + private static final JavaType.Class springRunnerType = JavaType.Class.build("org.springframework.test.context.junit4.SpringRunner"); + private static final JavaType.Class extendWithType = JavaType.Class.build("org.junit.jupiter.api.extension.ExtendWith"); + private static final J.Ident extendWithIdent = J.Ident.build( + randomId(), + "ExtendWith", + extendWithType, + EMPTY + ); + private static final JavaType.Class springExtensionType = JavaType.Class.build("org.springframework.test.context.junit.jupiter.SpringExtension"); + + + // Reference @RunWith(SpringRunner.class) annotation for semantically equal to compare against + private static final J.Annotation runWithSpringRunnerAnnotation = new J.Annotation( + randomId(), + runWithIdent, + new J.Annotation.Arguments( + randomId(), + Collections.singletonList( + new J.FieldAccess( + randomId(), + J.Ident.build( + randomId(), + "SpringRunner", + springRunnerType, + EMPTY + ), + J.Ident.build(randomId(), "class", null, EMPTY), + JavaType.Class.build("java.lang.Class"), + EMPTY + ) + ), + EMPTY + ), + EMPTY + ); + + private static final J.Annotation extendWithSpringExtensionAnnotation = new J.Annotation( + randomId(), + extendWithIdent, + new J.Annotation.Arguments( + randomId(), + Collections.singletonList( + new J.FieldAccess( + randomId(), + J.Ident.build( + randomId(), + "SpringExtension", + springExtensionType, + EMPTY + ), + J.Ident.build(randomId(), "class", null, EMPTY), + JavaType.Class.build("java.lang.Class"), + EMPTY + ) + ), + EMPTY + ), + EMPTY + ); + + public SpringRunnerToSpringExtension() { + setCursoringOn(); + } + + @Override + public J.ClassDecl visitClassDecl(J.ClassDecl cd) { + List annotations = cd.getAnnotations().stream() + .map(this::springRunnerToSpringExtension) + .collect(Collectors.toList()); + + return cd.withAnnotations(annotations); + } + + /** + * Converts annotations like @RunWith(SpringRunner.class) into @ExtendWith(SpringExtension.class) + * Leaves other annotations untouched and returns as-is. + * + * NOT a pure function. Side effects include: + * Adding imports for ExtendWith and SpringExtension + * Removing imports for RunWith and SpringRunner + */ + private J.Annotation springRunnerToSpringExtension(J.Annotation maybeSpringRunner) { + if(!new SemanticallyEqual(runWithSpringRunnerAnnotation).visit(maybeSpringRunner)) { + return maybeSpringRunner; + } + Formatting originalFormatting = maybeSpringRunner.getFormatting(); + J.ClassDecl parent = getCursor().firstEnclosing(J.ClassDecl.class); + assert parent != null; + J.Annotation extendWithSpringExtension = extendWithSpringExtensionAnnotation.withFormatting(originalFormatting); + maybeAddImport(extendWithType); + maybeAddImport(springExtensionType); + maybeRemoveImport(springRunnerType); + maybeRemoveImport(runWithType); + + return extendWithSpringExtension; + } +} diff --git a/src/test/kotlin/org/openrewrite/java/testing/junit5/SpringRunnerToSpringExtensionTest.kt b/src/test/kotlin/org/openrewrite/java/testing/junit5/SpringRunnerToSpringExtensionTest.kt new file mode 100755 index 000000000..b4edaf05d --- /dev/null +++ b/src/test/kotlin/org/openrewrite/java/testing/junit5/SpringRunnerToSpringExtensionTest.kt @@ -0,0 +1,66 @@ +/* + * 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.junit5 + +import org.junit.Test +import org.openrewrite.RefactorVisitorTestForParser +import org.openrewrite.java.JavaParser +import org.openrewrite.java.tree.J + +class SpringRunnerToSpringExtensionTest : RefactorVisitorTestForParser { + override val parser: JavaParser = JavaParser.fromJavaVersion() + .classpath("junit", "spring-test") + .build() + + override val visitors = listOf(SpringRunnerToSpringExtension()) + + @Test + fun basicRunnerToExtension() = assertRefactored( + before = """ + import org.junit.runner.RunWith; + import org.springframework.test.context.junit4.SpringRunner; + + @RunWith(SpringRunner.class) + class A {} + """, + after = """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.springframework.test.context.junit.jupiter.SpringExtension; + + @ExtendWith(SpringExtension.class) + class A {} + """ + ) + + @Test + fun leavesOtherRunnersAlone() = assertUnchanged( + before = """ + package a; + + import org.junit.runner.RunWith; + + @RunWith(B.class) + class A {} + """, + dependencies = listOf( + """ + package a; + + class B {} + """ + ) + ) +}