Skip to content

Commit

Permalink
AddMissingTestBeforeAfterAnnotations recipe for overridden JUnit te…
Browse files Browse the repository at this point in the history
…st methods (#444)

* fix-issue-441

* fix issue 443

* Add precondition & push logic into LifecyleAnnotation

* Remove trailing whitespace and excess newlines

---------

Co-authored-by: Thomas Mauch <[email protected]>
Co-authored-by: Tim te Beek <[email protected]>
  • Loading branch information
3 people authored Dec 19, 2023
1 parent e7f0148 commit 6a9f229
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.AnnotationMatcher;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType.FullyQualified;
import org.openrewrite.java.tree.JavaType.Method;
import org.openrewrite.java.tree.TypeUtils;
import org.openrewrite.marker.SearchResult;

import java.util.Comparator;
import java.util.Optional;
import java.util.function.Predicate;

@Value
@EqualsAndHashCode(callSuper = false)
public class AddMissingTestBeforeAfterAnnotations extends Recipe {
@Override
public String getDisplayName() {
return "Add missing `@BeforeEach`, `@AfterEach`, `@Test` to overriding methods";
}

@Override
public String getDescription() {
return "Adds `@BeforeEach`, `@AfterEach`, `@Test` to methods overriding superclass methods if the annoations are present on the superclass method.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
if (classDecl.getExtends() != null) {
// Only classes that extend other classes can have override methods with missing annotations
return SearchResult.found(classDecl);
}
return super.visitClassDeclaration(classDecl, ctx);
}
}, new AddMissingTestBeforeAfterAnnotationsVisitor());
}

private static class AddMissingTestBeforeAfterAnnotationsVisitor extends JavaIsoVisitor<ExecutionContext> {
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
if (!method.hasModifier(J.Modifier.Type.Static) && !method.isConstructor()) {
Optional<Method> superMethod = TypeUtils.findOverriddenMethod(method.getMethodType());
if (superMethod.isPresent()) {
method = maybeAddMissingAnnotation(method, superMethod.get(), LifecyleAnnotation.BEFORE_EACH, ctx);
method = maybeAddMissingAnnotation(method, superMethod.get(), LifecyleAnnotation.AFTER_EACH, ctx);
method = maybeAddMissingAnnotation(method, superMethod.get(), LifecyleAnnotation.TEST, ctx);
}
}
return super.visitMethodDeclaration(method, ctx);
}

private J.MethodDeclaration maybeAddMissingAnnotation(J.MethodDeclaration method, Method superMethod, LifecyleAnnotation la, ExecutionContext ctx) {
if (la.hasOldAnnotation(superMethod) && !la.hasNewAnnotation(method)) {
maybeAddImport(la.annotation);
return JavaTemplate.builder(la.simpleAnnotation)
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-jupiter-api-5.9"))
.imports(la.annotation)
.build()
.apply(getCursor(), method.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)));
}
return method;
}
}

enum LifecyleAnnotation {
BEFORE_EACH("org.junit.jupiter.api.BeforeEach", "org.junit.Before"),
AFTER_EACH("org.junit.jupiter.api.AfterEach", "org.junit.After"),
TEST("org.junit.jupiter.api.Test", "org.junit.Test");

String annotation;
String simpleAnnotation;
private Predicate<FullyQualified> oldAnnotationPredicate;
private AnnotationMatcher annotationMatcher;

LifecyleAnnotation(String annotation, String oldAnnotation) {
this.annotation = annotation;
this.simpleAnnotation = "@" + annotation.substring(annotation.lastIndexOf(".") + 1);
this.oldAnnotationPredicate = n -> TypeUtils.isOfClassType(n, oldAnnotation);
this.annotationMatcher = new AnnotationMatcher("@" + annotation);
}

boolean hasOldAnnotation(Method method) {
return method.getAnnotations().stream().anyMatch(oldAnnotationPredicate);
}

boolean hasNewAnnotation(J.MethodDeclaration method) {
return method.getAllAnnotations().stream().anyMatch(annotationMatcher::matches);
}
}
}
1 change: 1 addition & 0 deletions src/main/resources/META-INF/rewrite/junit5.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ recipeList:
- org.openrewrite.java.testing.junit5.UpdateBeforeAfterAnnotations
- org.openrewrite.java.testing.junit5.LifecycleNonPrivate
- org.openrewrite.java.testing.junit5.UpdateTestAnnotation
- org.openrewrite.java.testing.junit5.AddMissingTestBeforeAfterAnnotations
- org.openrewrite.java.testing.junit5.ParameterizedRunnerToParameterized
- org.openrewrite.java.testing.junit5.JUnitParamsRunnerToParameterized
- org.openrewrite.java.testing.junit5.ExpectedExceptionToAssertThrows
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2021 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.jupiter.api.Test;
import org.openrewrite.InMemoryExecutionContext;
import org.openrewrite.java.JavaParser;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.java.Assertions.java;

class AddMissingTestBeforeAfterAnnotationsTest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec
.parser(JavaParser.fromJavaVersion()
.classpathFromResources(new InMemoryExecutionContext(), "junit-4.13", "junit-jupiter-api-5.9"))
.recipe(new AddMissingTestBeforeAfterAnnotations());
}

@Test
void addMissingTestBeforeAfterAnnotations() {
//language=java
rewriteRun(
java(
"""
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class AbstractTest {
@Before
public void before() {
}
@After
public void after() {
}
@Test
public void test() {
}
}
"""
),
java(
"""
public class A extends AbstractTest {
public void before() {
}
public void after() {
}
public void test() {
}
}
""",
"""
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class A extends AbstractTest {
@BeforeEach
public void before() {
}
@AfterEach
public void after() {
}
@Test
public void test() {
}
}
"""
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,86 @@ void test() {
)
);
}

@Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/443")
@Test
void migrateInheritedTestBeforeAfterAnnotations() {
//language=java
rewriteRun(
java(
"""
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class AbstractTest {
@Before
public void before() {
}
@After
public void after() {
}
@Test
public void test() {
}
}
""",
"""
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class AbstractTest {
@BeforeEach
public void before() {
}
@AfterEach
public void after() {
}
@Test
public void test() {
}
}
"""
),
java(
"""
public class A extends AbstractTest {
public void before() {
}
public void after() {
}
public void test() {
}
}
""",
"""
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class A extends AbstractTest {
@BeforeEach
public void before() {
}
@AfterEach
public void after() {
}
@Test
public void test() {
}
}
"""
)
);
}

}

0 comments on commit 6a9f229

Please sign in to comment.