Skip to content

Commit

Permalink
Add recipes to migrate from TestNG to AssertJ (#520)
Browse files Browse the repository at this point in the history
* Copy JUnit classes to TestNG

* Adapt copied JUnit classes to TestNG

* Add a first recipe test reusing Picnic's TestNG rules

* Apply suggestions from code review

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Move test and assert static imports inserted

* Update assertions and enable custom recipes again

* Use `classpath` instead of missing `classpathFromResources`

* Inline `JavaParser.Builder` and `Visitor`

* Use `UsesMethod` as precondition

* Enable all custom recipes again

* Drop recipes also picked up from `TestNGToAssertJRulesRecipes`

* Message is always a String

---------

Co-authored-by: Tim te Beek <[email protected]>
Co-authored-by: Tim te Beek <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Dec 10, 2024
1 parent f0d765a commit f9005f1
Show file tree
Hide file tree
Showing 6 changed files with 492 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ dependencies {
exclude(group = "org.yaml", module = "snakeyaml")
}
testRuntimeOnly("org.easymock:easymock:latest.release")
testRuntimeOnly("org.testng:testng:latest.release")
testRuntimeOnly("org.mockito.kotlin:mockito-kotlin:latest.release")
testRuntimeOnly("org.testcontainers:testcontainers:latest.release")
testRuntimeOnly("org.testcontainers:nginx:latest.release")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2024 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.testng;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.search.UsesMethod;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;

import java.util.List;

public class TestNgAssertEqualsToAssertThat extends Recipe {

@Override
public String getDisplayName() {
return "TestNG `assertEquals` to AssertJ";
}

@Override
public String getDescription() {
return "Convert TestNG-style `assertEquals()` to AssertJ's `assertThat().isEqualTo()`.";
}

private static final MethodMatcher TESTNG_ASSERT_METHOD = new MethodMatcher("org.testng.Assert assertEquals(..)");

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesMethod<>(TESTNG_ASSERT_METHOD), new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
if (!TESTNG_ASSERT_METHOD.matches(method)) {
return method;
}

List<Expression> args = method.getArguments();
Expression expected = args.get(1);
Expression actual = args.get(0);

//always add the import (even if not referenced)
maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false);

// Remove import for "org.testng.Assert" if no longer used.
maybeRemoveImport("org.testng.Assert");

if (args.size() == 2) {
return JavaTemplate.builder("assertThat(#{any()}).isEqualTo(#{any()});")
.staticImports("org.assertj.core.api.Assertions.assertThat")
.javaParser(JavaParser.fromJavaVersion()
.classpathFromResources(ctx, "assertj-core-3.24"))
.build()
.apply(getCursor(), method.getCoordinates().replace(), actual, expected);
} else if (args.size() == 3 && !isFloatingPointType(args.get(2))) {
Expression message = args.get(2);
return JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isEqualTo(#{any()});")
.staticImports("org.assertj.core.api.Assertions.assertThat")
.imports("java.util.function.Supplier")
.javaParser(JavaParser.fromJavaVersion()
.classpathFromResources(ctx, "assertj-core-3.24"))
.build()
.apply(
getCursor(),
method.getCoordinates().replace(),
actual,
message,
expected
);
} else if (args.size() == 3) {
//always add the import (even if not referenced)
maybeAddImport("org.assertj.core.api.Assertions", "within", false);
return JavaTemplate.builder("assertThat(#{any()}).isCloseTo(#{any()}, within(#{any()}));")
.staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within")
.javaParser(JavaParser.fromJavaVersion()
.classpathFromResources(ctx, "assertj-core-3.24"))
.build()
.apply(getCursor(), method.getCoordinates().replace(), actual, expected, args.get(2));

}

// The assertEquals is using a floating point with a delta argument and a message.
Expression message = args.get(3);

//always add the import (even if not referenced)
maybeAddImport("org.assertj.core.api.Assertions", "within", false);
return JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isCloseTo(#{any()}, within(#{any()}));")
.staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within")
.imports("java.util.function.Supplier")
.javaParser(JavaParser.fromJavaVersion()
.classpathFromResources(ctx, "assertj-core-3.24"))
.build()
.apply(
getCursor(),
method.getCoordinates().replace(),
actual,
message,
expected,
args.get(2)
);
}

private boolean isFloatingPointType(Expression expression) {

JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType());
if (fullyQualified != null) {
String typeName = fullyQualified.getFullyQualifiedName();
return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName);
}

JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType());
return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float;
}
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Copyright 2024 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.testng;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.search.UsesMethod;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;

import java.util.List;

public class TestNgAssertNotEqualsToAssertThat extends Recipe {

@Override
public String getDisplayName() {
return "TestNG `assertNotEquals` to AssertJ";
}

@Override
public String getDescription() {
return "Convert TestNG-style `assertNotEquals()` to AssertJ's `assertThat().isNotEqualTo()`.";
}

private static final MethodMatcher TESTNG_ASSERT_METHOD = new MethodMatcher("org.testng.Assert assertNotEquals(..)");

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesMethod<>(TESTNG_ASSERT_METHOD), new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
if (!TESTNG_ASSERT_METHOD.matches(method)) {
return method;
}

List<Expression> args = method.getArguments();

Expression expected = args.get(1);
Expression actual = args.get(0);

if (args.size() == 2) {
method = JavaTemplate.builder("assertThat(#{any()}).isNotEqualTo(#{any()});")
.staticImports("org.assertj.core.api.Assertions.assertThat")
.javaParser(JavaParser.fromJavaVersion()
.classpathFromResources(ctx, "assertj-core-3.24"))
.build()
.apply(
getCursor(),
method.getCoordinates().replace(),
actual,
expected
);
} else if (args.size() == 3 && !isFloatingPointType(args.get(2))) {
Expression message = args.get(2);
method = JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isNotEqualTo(#{any()});")
.staticImports("org.assertj.core.api.Assertions.assertThat")
.javaParser(JavaParser.fromJavaVersion()
.classpathFromResources(ctx, "assertj-core-3.24"))
.build()
.apply(
getCursor(),
method.getCoordinates().replace(),
actual,
message,
expected
);
} else if (args.size() == 3) {
method = JavaTemplate.builder("assertThat(#{any()}).isNotCloseTo(#{any()}, within(#{any()}));")
.staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within")
.javaParser(JavaParser.fromJavaVersion()
.classpathFromResources(ctx, "assertj-core-3.24"))
.build()
.apply(
getCursor(),
method.getCoordinates().replace(),
actual,
expected,
args.get(2)
);
maybeAddImport("org.assertj.core.api.Assertions", "within", false);
} else {
Expression message = args.get(3);
method = JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isNotCloseTo(#{any()}, within(#{any()}));")
.staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within")
.javaParser(JavaParser.fromJavaVersion()
.classpathFromResources(ctx, "assertj-core-3.24"))
.build()
.apply(
getCursor(),
method.getCoordinates().replace(),
actual,
message,
expected,
args.get(2)
);

maybeAddImport("org.assertj.core.api.Assertions", "within", false);
}

//Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced)
maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false);

// Remove import for "org.testng.Assert" if no longer used.
maybeRemoveImport("org.testng.Assert");

return method;
}

private boolean isFloatingPointType(Expression expression) {
JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType());
if (fullyQualified != null) {
String typeName = fullyQualified.getFullyQualifiedName();
return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName);
}

JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType());
return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float;
}
});
}

}
1 change: 1 addition & 0 deletions src/main/resources/META-INF/rewrite/assertj.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ tags:
recipeList:
- org.openrewrite.java.testing.hamcrest.MigrateHamcrestToAssertJ
- org.openrewrite.java.testing.assertj.JUnitToAssertj
- org.openrewrite.java.testing.testng.TestNgToAssertj
- org.openrewrite.java.testing.assertj.StaticImports
- org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertions
- org.openrewrite.java.testing.assertj.SimplifyAssertJAssertions
Expand Down
34 changes: 34 additions & 0 deletions src/main/resources/META-INF/rewrite/testng.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#
# Copyright 2024 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.
#
---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.testing.testng.TestNgToAssertj
displayName: Migrate TestNG assertions to AssertJ
description: Convert assertions from `org.testng.Assert` to `org.assertj.core.api.Assertions`.
tags:
- assertj
- testing
- testng
recipeList:
- org.openrewrite.java.dependencies.AddDependency:
groupId: org.assertj
artifactId: assertj-core
version: 3.x
onlyIfUsing: org.testng.*
acceptTransitive: true
- tech.picnic.errorprone.refasterrules.TestNGToAssertJRulesRecipes
- org.openrewrite.java.testing.testng.TestNgAssertEqualsToAssertThat
- org.openrewrite.java.testing.testng.TestNgAssertNotEqualsToAssertThat
Loading

0 comments on commit f9005f1

Please sign in to comment.