Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Hamcrest to JUnit 5 #343

Merged
merged 41 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4b2e427
WIP: Add recipe for migration from Hamcrest
matusmatokpt May 22, 2023
e8ffbae
Merge branch 'openrewrite:main' into main
matusmatokpt May 22, 2023
0bb2ffa
Merge branch 'openrewrite:main' into main
matusmatokpt May 23, 2023
0947005
Add missing license headers
timtebeek May 23, 2023
11d9fd2
Resolve some of the test issues
timtebeek May 23, 2023
cc4a929
Fix test import
timtebeek May 23, 2023
bc1d5c8
Add proto implementation for assertEquals
matusmatokpt May 24, 2023
e17fc29
Use static import and #{any(java.lang.Object)} to fix test
timtebeek May 24, 2023
76724ca
Merge branch 'main' into main
matusmatokpt Jun 13, 2023
e360972
Adapt to main
matusmatokpt Jun 13, 2023
66473e8
Add more simple matcher-to-method translations
matusmatokpt Jun 15, 2023
cc581eb
Add more simple matcher-to-method translations
matusmatokpt Jun 22, 2023
0eb3887
Add tests
matusmatokpt Jun 27, 2023
7d11579
Finalise the pull request
matusmatokpt Jun 29, 2023
1730fbf
Merge branch 'main' into main
matusmatokpt Jun 29, 2023
3525302
Merge branch 'main' into main
timtebeek Jun 30, 2023
9357f7a
Add required license header
timtebeek Jun 30, 2023
9e23d71
Move classes to align with the Hamcrest to AssertJ implementation
timtebeek Jun 30, 2023
d75e50f
Consistently use `class Test` to avoid conflicts with `@Test`
timtebeek Jun 30, 2023
21d4728
Refactored and split HamcrestMatcherToJUnit5 recipe
matusmatokpt Aug 2, 2023
5395d63
Merge branch 'main' into main
matusmatokpt Aug 2, 2023
ff0e775
Add license headers
matusmatokpt Aug 2, 2023
483fcb4
Merge branch 'main' into main
timtebeek Nov 21, 2023
26a8646
Merge branch 'main' into main
timtebeek Feb 5, 2024
93448e3
Apply suggestions from code review
timtebeek Jun 15, 2024
be54077
Merge branch 'main' into main
timtebeek Jun 15, 2024
be57cfe
Apply suggestions from code review
timtebeek Jun 15, 2024
a18e7ac
Merge branch 'main' into main
timtebeek Nov 30, 2024
6549dcf
Merge branch 'main' into main
timtebeek Dec 10, 2024
7d29ecb
Fix compilation
timtebeek Dec 10, 2024
04cc54d
Update description to use JUnit
timtebeek Dec 10, 2024
32f912b
Apply suggestions from code review
timtebeek Dec 10, 2024
edc7dcb
Format tests
timtebeek Dec 10, 2024
ba008de
Drop RemoveNotMatcher recipe; retain visitor only
timtebeek Dec 10, 2024
8b69167
Drop AssertThatBooleanToJUnit5; replace with declarative recipes
timtebeek Dec 10, 2024
ea5043f
Apply suggestions from code review
timtebeek Dec 10, 2024
59a16fa
Limit execution through preconditions
timtebeek Dec 10, 2024
8c9ba4f
Add preconditions to HamcrestMatcherToJUnit5
timtebeek Dec 10, 2024
f42db20
Minor polish
timtebeek Dec 10, 2024
edd3b48
Extract and reuse `ConsistentHamcrestMatcherImports`
timtebeek Dec 10, 2024
f66a2ec
Polish recipe display name and description
timtebeek Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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;
timtebeek marked this conversation as resolved.
Show resolved Hide resolved

import org.openrewrite.ExecutionContext;
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.tree.Expression;
import org.openrewrite.java.tree.J;

import java.util.List;

public class MigrateFromHamcrest extends Recipe {
@Override
public String getDisplayName() {
return "Migrate from Hamcrest Matchers to JUnit5";
}

@Override
public String getDescription() {
return "This recipe will migrate all Hamcrest Matchers to JUnit5 assertions.";
}

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

private static class MigrationFromHamcrestVisitor extends JavaIsoVisitor<ExecutionContext> {

@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
J.MethodInvocation mi = super.visitMethodInvocation(method, executionContext);
MethodMatcher matcherAssertTrue = new MethodMatcher("org.hamcrest.MatchAssert assertThat(String, boolean)");
MethodMatcher matcherAssertMatcher = new MethodMatcher("org.hamcrest.MatcherAssert assertThat(*, org.hamcrest.Matcher)");
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
MethodMatcher matcherAssertMatcherWithReason = new MethodMatcher("org.hamcrest.MatcherAssert assertThat(String,*,org.hamcrest.Matcher)");

if (matcherAssertTrue.matches(mi)) {
//TODO simple
} else if (matcherAssertMatcher.matches(mi)) {
Expression hamcrestMatcher = mi.getArguments().get(1);
if (hamcrestMatcher instanceof J.MethodInvocation) {
J.MethodInvocation matcherInvocation = (J.MethodInvocation)hamcrestMatcher;
maybeRemoveImport("org.hamcrest.Matchers." + matcherInvocation.getSimpleName());
maybeRemoveImport("org.hamcrest.MatcherAssert.assertThat");
String targetAssertion = getTranslatedAssert(matcherInvocation);
JavaTemplate template = JavaTemplate.builder(this::getCursor, getTemplateForTranslatedAssertion(targetAssertion))
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(executionContext, "junit-jupiter-api-5.9"))
.staticImports("org.junit.jupiter.api.Assertions." + targetAssertion)
.build();
mi = withTemplate(mi, template, mi.getArguments().get(0), stripMatcherInvocation(mi.getArguments().get(1)));
maybeAddImport("org.junit.jupiter.api.Assertions", targetAssertion);
}
else throw new IllegalArgumentException("Parameter mismatch for " + mi + ".");
}
return mi;
}

private J.MethodInvocation withTemplate(J.MethodInvocation method, JavaTemplate template, Expression firstArg, List<Expression> matcherArgs) {
switch (matcherArgs.size()) {
case 0:
return method.withTemplate(template, method.getCoordinates().replace(), firstArg);
case 1:
return method.withTemplate(template, method.getCoordinates().replace(), firstArg, matcherArgs.get(0));
case 2 :
return method.withTemplate(template, method.getCoordinates().replace(), firstArg, matcherArgs.get(0), matcherArgs.get(1));
case 3 :
return method.withTemplate(template, method.getCoordinates().replace(), firstArg, matcherArgs.get(0), matcherArgs.get(1), matcherArgs.get(2));
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
default:
throw new IllegalArgumentException("List of matcher arguments is too long for " + method + ".");
}
}

private List<Expression> stripMatcherInvocation(Expression e) {
if (e instanceof J.MethodInvocation) {
MethodMatcher matchesMatcher = new MethodMatcher("org.hamcrest.Matchers *(..)");
if (matchesMatcher.matches(e)) {
return ((J.MethodInvocation) e).getArguments();
}
}
throw new IllegalArgumentException("Trying to strip an expression which is not a matcher invocation:\n" + e);
}

private String getTranslatedAssert(J.MethodInvocation methodInvocation) {
//to be replaced with a static map
switch (methodInvocation.getSimpleName()) {
case "equalTo":
return "assertEquals";
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
}
throw new IllegalArgumentException("Translation of matcher " + methodInvocation.getSimpleName() + " not yet supported.");
}

private String getTemplateForTranslatedAssertion(String translatedAssertion) {
//to be replaced with a static map
switch (translatedAssertion) {
case "assertEquals":
return "assertEquals(#{any(java.lang.Object)}, #{any(java.lang.Object)})";
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
}
throw new IllegalArgumentException("There is no template defined for assertion " + translatedAssertion);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 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 MigrateFromHamcrestTest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec
.parser(JavaParser.fromJavaVersion()
.classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api-5.9", "hamcrest-2.2"))
.recipe(new MigrateFromHamcrest());
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
}

@Test
void equalToObject() {
//language=java
rewriteRun(
java("""
class Biscuit {
String name;
Biscuit(String name) {
this.name = name;
}
}
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
"""),
java(
"""
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

class BiscuitTest {
@Test
void testEquals() {
Biscuit theBiscuit = new Biscuit("Ginger");
Biscuit myBiscuit = new Biscuit("Ginger");
assertThat(theBiscuit, equalTo(myBiscuit));
}
}
""",
"""
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class BiscuitTest {
@Test
void testEquals() {
Biscuit theBiscuit = new Biscuit("Ginger");
Biscuit myBiscuit = new Biscuit("Ginger");
assertEquals(theBiscuit, myBiscuit);
}
}
"""
));
}
}