Skip to content

Commit

Permalink
Add Recipe for migrating JUnit4 @RunWith(Parameterized.class) to the …
Browse files Browse the repository at this point in the history
…Junit 5 Jupiter ParameterizedTest equivalent. Fixes #81 (#89)
  • Loading branch information
pway99 authored Mar 24, 2021
1 parent 9fd08ae commit f1d4545
Show file tree
Hide file tree
Showing 2 changed files with 357 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package org.openrewrite.java.testing.junit5;

import org.openrewrite.Cursor;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.AnnotationMatcher;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.tree.*;
import org.openrewrite.marker.Markers;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static org.openrewrite.Tree.randomId;

/**
* Recipe for converting @RunWith(@Parameterized.class) to @ParameterizedTests with @MethodSource
* <p>
* 1. Remove `@RunWith(Parameterized.class)`
* 2. Replace `@Test` with `@ParameterizedTest` having arguments from `@Parameters` method
* 3. Add `@MethodSource(...)` with argument equal to `@Parameters` method name to each `@ParameterizedTest`
* 4. Remove @Parameters annotation
* 5. Change constructor to an initialization method having a void return type.
* 6. For each `@ParameterizedTest` Insert statement to Invoke initialization method with test parameters
* 7. Remove imports
* org.junit.Test;
* org.junit.runner.RunWith;
* org.junit.runners.Parameterized;
* org.junit.runners.Parameterized.Parameters;
* 8. Add imports
* org.junit.jupiter.params.ParameterizedTest;
* org.junit.jupiter.params.provider.MethodSource;
*/
public class ParameterizedRunnerToParameterized extends Recipe {

private static final AnnotationMatcher RUN_WITH_PARAMETERS_ANNOTATION_MATCHER = new AnnotationMatcher("@org.junit.runner.RunWith(org.junit.runners.Parameterized.class)");
private static final AnnotationMatcher TEST_ANNOTATION_MATCHER = new AnnotationMatcher("@org.junit.Test");
private static final AnnotationMatcher PARAMETERS_MATCHER = new AnnotationMatcher("@org.junit.runners.Parameterized.Parameters");
private static final AnnotationMatcher PARAMETERIZED_TEST_ANNOTATION_MATCHER = new AnnotationMatcher("@org.junit.jupiter.params.ParameterizedTest");

private static final String PARAMETERIZED_TEST_ANNOTATION_PARAMETERS = "parameterizedTestParameters";
private static final String PARAMETERIZED_TEST_METHOD_PARAMETERS = "parameterizedTestMethodParameters";
private static final String METHOD_REFERENCE_NAME = "methodReferenceName";
private static final String INIT_METHOD_NAME = "initMethodName";

private static final ThreadLocal<JavaParser> PARAMETERIZED_TEMPLATE_PARSER = ThreadLocal.withInitial(() ->
JavaParser.fromJavaVersion().build()
);

@Override
public String getDisplayName() {
return "JUnit4 @RunWith(Parameterized.class) to JUnit Jupiter Parameterized Tests";
}

@Override
public String getDescription() {
return "Convert JUnit4 Parameterized runner the JUnit Jupiter ParameterizedTest equivalent.";
}

@Override
protected TreeVisitor<?, ExecutionContext> getVisitor() {
return new ParameterizedRunnerVisitor();
}

/**
* Visitor for collecting Parameterized Test components and then scheduling the appropriate conversion visitor for the next visit
*/
protected class ParameterizedRunnerVisitor extends JavaIsoVisitor<ExecutionContext> {

@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, executionContext);

String methodReferenceName = getCursor().getMessage(METHOD_REFERENCE_NAME);
String initMethodName = getCursor().getMessage(INIT_METHOD_NAME);
List<Expression> testAnnotationParams = getCursor().getMessage(PARAMETERIZED_TEST_ANNOTATION_PARAMETERS);
List<Statement> testMethodParams = getCursor().getMessage(PARAMETERIZED_TEST_METHOD_PARAMETERS);

// Condition for converting to ParameterizedTest with MethodParams
if (methodReferenceName != null && testMethodParams != null && initMethodName != null) {
doAfterVisit(new ParameterizedTestWithMethodSourceVisitor(methodReferenceName, initMethodName, testAnnotationParams, testMethodParams));
}
return cd;
}

@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) {
J.MethodDeclaration m = super.visitMethodDeclaration(method, executionContext);
Cursor classDeclCursor = getCursor().dropParentUntil(J.ClassDeclaration.class::isInstance);
m = m.withLeadingAnnotations(ListUtils.map(m.getLeadingAnnotations(), anno -> {
if (PARAMETERS_MATCHER.matches(anno)) {
classDeclCursor.putMessage(PARAMETERIZED_TEST_ANNOTATION_PARAMETERS, anno.getArguments());
classDeclCursor.putMessage(METHOD_REFERENCE_NAME, method.getSimpleName());
}
return anno;
}));
if (m.isConstructor()) {
classDeclCursor.putMessage(PARAMETERIZED_TEST_METHOD_PARAMETERS, m.getParameters());
classDeclCursor.putMessage(INIT_METHOD_NAME, "init" + m.getSimpleName());
}
return m;
}

/**
* Visitor for converting Parameterized runner to Parameterized Tests with an associated MethodSource
*/
protected class ParameterizedTestWithMethodSourceVisitor extends JavaIsoVisitor<ExecutionContext> {
private final String methodReference;
private final String initMethodName;
private final List<Expression> parameterizedTestAnnotationParameters;
private final List<Statement> parameterizedTestMethodParameters;
private final String initStatementParams;
private final String parameterizedTestAnnotationTemplate;

public ParameterizedTestWithMethodSourceVisitor(String methodReference, String initMethodName, List<Expression> parameterizedTestAnnotationParameters, List<Statement> parameterizedTestMethodParameters) {
this.methodReference = methodReference;
this.initMethodName = initMethodName;
this.parameterizedTestAnnotationParameters = parameterizedTestAnnotationParameters;
this.parameterizedTestMethodParameters = parameterizedTestMethodParameters;
this.initStatementParams = parameterizedTestMethodParameters.stream()
.map(J.VariableDeclarations.class::cast)
.map(n -> {
return n.getVariables().get(0).getSimpleName();
})
.collect(Collectors.joining(", "));
this.parameterizedTestAnnotationTemplate = parameterizedTestAnnotationParameters != null ? "@ParameterizedTest(" + parameterizedTestAnnotationParameters.get(0).print() + ")" : "@ParameterizedTest";
}

@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, executionContext);
// Remove @RunWith(Parameterized.class) annotation
cd = cd.withLeadingAnnotations(ListUtils.map(cd.getLeadingAnnotations(), anno -> {
if (RUN_WITH_PARAMETERS_ANNOTATION_MATCHER.matches(anno)) {
return null;
}
return anno;
}));

// Update Imports
maybeRemoveImport("org.junit.Test");
maybeRemoveImport("org.junit.runner.RunWith");
maybeRemoveImport("org.junit.runners.Parameterized");
maybeRemoveImport("org.junit.runners.Parameterized.Parameters");
maybeAddImport("org.junit.jupiter.params.ParameterizedTest");
maybeAddImport("org.junit.jupiter.params.provider.MethodSource");
return cd;
}

@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) {
J.MethodDeclaration m = super.visitMethodDeclaration(method, executionContext);

// Remove the @Parameters annotation
m = m.withLeadingAnnotations(ListUtils.map(m.getLeadingAnnotations(), anno -> {
if (PARAMETERS_MATCHER.matches(anno)) {
return null;
}
return anno;
}));

// Replace the @Test with @ParameterizedTest
m = m.withLeadingAnnotations(ListUtils.map(m.getLeadingAnnotations(), anno -> {
if (TEST_ANNOTATION_MATCHER.matches(anno)) {
anno = anno.withTemplate(template(parameterizedTestAnnotationTemplate)
.javaParser(PARAMETERIZED_TEMPLATE_PARSER.get())
.imports("org.junit.jupiter.params.ParameterizedTest").build(),
anno.getCoordinates().replace());
}
return anno;
}));

// Add @MethodSource, insert test init statement, add test method parameters
if (m.getLeadingAnnotations().stream().anyMatch(PARAMETERIZED_TEST_ANNOTATION_MATCHER::matches)) {
m = m.withTemplate(template("@MethodSource(\"" + methodReference + "\")")
.javaParser(PARAMETERIZED_TEMPLATE_PARSER.get())
.imports("org.junit.jupiter.params.provider.MethodSource").build(),
m.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName)));
m = m.withTemplate(template(initMethodName + "(#{});")
.javaParser(PARAMETERIZED_TEMPLATE_PARSER.get())
.build(), m.getBody().getStatements().get(0).getCoordinates().before(), initStatementParams);
m = m.withParameters(parameterizedTestMethodParameters);
}

// Change constructor to test init method
if (m.isConstructor()) {
m = m.withName(m.getName().withName(initMethodName));
m = maybeAutoFormat(m, m.withReturnTypeExpression(new J.Primitive(randomId(), Space.EMPTY, Markers.EMPTY, JavaType.Primitive.Void)),
executionContext, getCursor().dropParentUntil(J.class::isInstance));
}
return m;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package org.openrewrite.java.testing.junit5

import org.junit.jupiter.api.Test
import org.openrewrite.Recipe
import org.openrewrite.java.JavaParser
import org.openrewrite.java.JavaRecipeTest

class ParameterizedRunnerToParameterizedTest : JavaRecipeTest {

override val parser: JavaParser = JavaParser.fromJavaVersion()
.classpath("junit")
.build()
override val recipe: Recipe
get() = ParameterizedRunnerToParameterized()

@Test
fun parametersNameHasParameters() = assertChanged(
before = """
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class VetTests {
private String firstName;
private String lastName;
private Integer id;
public VetTests(String firstName, String lastName, Integer id) {
this.firstName = firstName;
this.lastName = lastName;
this.id = id;
}
@Test
public void testSerialization() {
Vet vet = new Vet();
vet.setFirstName(firstName);
vet.setLastName(lastName);
vet.setId(id);
}
@Parameters(name="{index}: {0} {1} - {2}")
public static List<Object[]> parameters() {
return Arrays.asList(
new Object[] { "Otis", "TheDog", 124 },
new Object[] { "Garfield", "TheBoss", 126 });
}
}
""",
after = """
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
public class VetTests {
private String firstName;
private String lastName;
private Integer id;
public void initVetTests(String firstName, String lastName, Integer id) {
this.firstName = firstName;
this.lastName = lastName;
this.id = id;
}
@MethodSource("parameters")
@ParameterizedTest(name = "{index}: {0} {1} - {2}")
public void testSerialization(String firstName, String lastName, Integer id) {
initVetTests(firstName, lastName, id);
Vet vet = new Vet();
vet.setFirstName(firstName);
vet.setLastName(lastName);
vet.setId(id);
}
public static List<Object[]> parameters() {
return Arrays.asList(
new Object[] { "Otis", "TheDog", 124 },
new Object[] { "Garfield", "TheBoss", 126 });
}
}
"""
)
@Test
fun parameterizedTestToParameterizedTestsWithMethodSource() = assertChanged(
before = """
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class VetTests {
private String firstName;
private String lastName;
private Integer id;
public VetTests(String firstName, String lastName, Integer id) {
this.firstName = firstName;
this.lastName = lastName;
this.id = id;
}
@Test
public void testSerialization() {
Vet vet = new Vet();
vet.setFirstName(firstName);
vet.setLastName(lastName);
vet.setId(id);
}
@Parameters
public static List<Object[]> parameters() {
return Arrays.asList(
new Object[] { "Otis", "TheDog", 124 },
new Object[] { "Garfield", "TheBoss", 126 });
}
}
""",
after = """
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
public class VetTests {
private String firstName;
private String lastName;
private Integer id;
public void initVetTests(String firstName, String lastName, Integer id) {
this.firstName = firstName;
this.lastName = lastName;
this.id = id;
}
@MethodSource("parameters")
@ParameterizedTest
public void testSerialization(String firstName, String lastName, Integer id) {
initVetTests(firstName, lastName, id);
Vet vet = new Vet();
vet.setFirstName(firstName);
vet.setLastName(lastName);
vet.setId(id);
}
public static List<Object[]> parameters() {
return Arrays.asList(
new Object[] { "Otis", "TheDog", 124 },
new Object[] { "Garfield", "TheBoss", 126 });
}
}
"""
)
}

0 comments on commit f1d4545

Please sign in to comment.