Skip to content

Commit

Permalink
Implemented AssertSameToAssertThat visitor (#21)
Browse files Browse the repository at this point in the history
Implemented visitor to convert JUnit's assertSame() to assertJ's assertThat().isSameAs()
  • Loading branch information
SofiaBrittoSchwartz authored Dec 15, 2020
1 parent ca93760 commit 5ad7208
Show file tree
Hide file tree
Showing 2 changed files with 366 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright 2020 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.junitassertj;

import org.openrewrite.AutoConfigure;
import org.openrewrite.java.*;
import org.openrewrite.java.tree.*;

import java.util.Collections;
import java.util.List;

import static org.openrewrite.Formatting.EMPTY;
import static org.openrewrite.Formatting.format;
import static org.openrewrite.Tree.randomId;
import static org.openrewrite.java.tree.MethodTypeBuilder.newMethodType;

/**
* This is a refactoring visitor that will convert JUnit-style assertSame() to assertJ's assertThat().isSameAs().
*
* This visitor only supports the migration of the following JUnit 5 assertSame() methods:
*
* <PRE>
* assertSame(Object expected, Object actual) -> assertThat(actual).isSameAs(expected)
* assertSame(Object expected, Object actual, String message) -> assertThat(actual).as(message).isSameAs(expected)
* assertSame(Object expected, Object actual, Supplier<String> messageSupplier) -> assertThat(actual).withFailMessage(messageSupplier).isSameAs(expected);
* </PRE>
*/
@AutoConfigure
public class AssertSameToAssertThat extends JavaIsoRefactorVisitor {

private static final String JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME = "org.junit.jupiter.api.Assertions";
private static final String ASSERTJ_QUALIFIED_ASSERTIONS_CLASS_NAME = "org.assertj.core.api.Assertions";
private static final String ASSERTJ_ASSERT_THAT_METHOD_NAME = "assertThat";

/**
* This matcher finds the junit methods that will be migrated by this visitor.
*/
private static final MethodMatcher JUNIT_ASSERT_SAME_MATCHER = new MethodMatcher(
JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME + " assertSame(..)"
);

private static final JavaType.Method assertThatMethodType = newMethodType()
.declaringClass(ASSERTJ_QUALIFIED_ASSERTIONS_CLASS_NAME)
.flags(Flag.Public, Flag.Static)
.returnType("org.assertj.core.api.AbstractBooleanAssert")
.name(ASSERTJ_ASSERT_THAT_METHOD_NAME)
.parameter(JavaType.Primitive.Boolean, "arg1")
.build();

@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method) {
J.MethodInvocation original = super.visitMethodInvocation(method);
if (!JUNIT_ASSERT_SAME_MATCHER.matches(method)) {
return original;
}

List<Expression> originalArgs = original.getArgs().getArgs();
Expression expected = originalArgs.get(0);
Expression actual = originalArgs.get(1);
Expression message = originalArgs.size() == 3 ? originalArgs.get(2) : null;

// This creates the `assertThat(<EXPRESSION>)` method invocation. Without the type information for this invocation,
// the AddImport visitor won't add a static import for "assertThat" (assuming it doesn't already exist)
// because it won't be able to find a reference to the type.
J.MethodInvocation assertSelect = new J.MethodInvocation(
randomId(),
null,
null,
J.Ident.build(randomId(), ASSERTJ_ASSERT_THAT_METHOD_NAME, JavaType.Primitive.Void, EMPTY),
new J.MethodInvocation.Arguments(
randomId(),
Collections.singletonList(actual.withPrefix("")),
EMPTY
),
assertThatMethodType,
EMPTY
);

// If the assertSame is the three-argument variant, we need to maintain the message via a chained method
// call to "as"/"withFailMessage". The message may be a String or Supplier<String>.
if (message != null) {
// In assertJ the "as" method has a more informative error message, but doesn't accept String suppliers
// so we're using "as" if the message is a string and "withFailMessage" if it is a supplier.
String messageAs = TypeUtils.isString(message.getType())? "as" : "withFailMessage";

assertSelect = new J.MethodInvocation(
randomId(),
assertSelect, // assertThat is the select for this method.
null,
J.Ident.build(randomId(), messageAs, null, EMPTY),
new J.MethodInvocation.Arguments(
randomId(),
Collections.singletonList(message.withPrefix("")),
EMPTY
),
null,
EMPTY
);
}

// This will always return the "isSameAs()" method using assertSelect as the select.
J.MethodInvocation replacement = new J.MethodInvocation(
randomId(),
assertSelect,
null,
J.Ident.build(randomId(), "isSameAs", JavaType.Primitive.Boolean, EMPTY),
new J.MethodInvocation.Arguments(
randomId(),
Collections.singletonList(expected.withPrefix("")),
EMPTY
),
null,
format("\n")
);
// Remove import for "org.junit.jupiter.api.Assertions" if no longer used.
maybeRemoveImport(JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME);

// Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat".
maybeAddImport(ASSERTJ_QUALIFIED_ASSERTIONS_CLASS_NAME, ASSERTJ_ASSERT_THAT_METHOD_NAME);

// Format the replacement method invocation in the context of where it is called.
andThen(new AutoFormat(replacement));
return replacement;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/*
* Copyright 2020 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.junitassertj

import org.junit.jupiter.api.Test
import org.openrewrite.Parser
import org.openrewrite.RefactorVisitor
import org.openrewrite.RefactorVisitorTestForParser
import org.openrewrite.java.JavaParser
import org.openrewrite.java.tree.J

class AssertSameToAssertThatTest : RefactorVisitorTestForParser<J.CompilationUnit> {
override val parser: Parser<J.CompilationUnit> = JavaParser.fromJavaVersion()
.classpath("junit-jupiter-api", "assertj-core", "apiguardian-api")
.build()

override val visitors: Iterable<RefactorVisitor<*>> = listOf(AssertSameToAssertThat())

@Test
fun singleStaticMethodNoMessage() = assertRefactored(
before = """
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertSame;
public class A {
@Test
public void test() {
String str = "String";
assertSame(notification(), str);
}
private String notification() {
return "String";
}
}
""",
after = """
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class A {
@Test
public void test() {
String str = "String";
assertThat(str).isSameAs(notification());
}
private String notification() {
return "String";
}
}
"""
)

@Test
fun singleStaticMethodWithMessageString() = assertRefactored(
before = """
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertSame;
public class A {
@Test
public void test() {
String str = "string";
assertSame(notification(), str, "Should be the same");
}
private String notification() {
return "String";
}
}
""",
after = """
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class A {
@Test
public void test() {
String str = "string";
assertThat(str).as("Should be the same").isSameAs(notification());
}
private String notification() {
return "String";
}
}
"""
)

@Test
fun singleStaticMethodWithMessageSupplier() = assertRefactored(
before = """
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertSame;
public class A {
@Test
public void test() {
String str = "string";
assertSame(notification(), str, () -> "Should be the same");
}
private String notification() {
return "String";
}
}
""",
after = """
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class A {
@Test
public void test() {
String str = "string";
assertThat(str).withFailMessage(() -> "Should be the same").isSameAs(notification());
}
private String notification() {
return "String";
}
}
"""
)

@Test
fun inlineReference() = assertRefactored(
before = """
import org.junit.jupiter.api.Test;
public class A {
@Test
public void test() {
String str = "string";
org.junit.jupiter.api.Assertions.assertSame(notification(), str);
org.junit.jupiter.api.Assertions.assertSame(notification(), str, "Should be the same");
org.junit.jupiter.api.Assertions.assertSame(notification(), str, () -> "Should be the same");
}
private String notification() {
return "String";
}
}
""",
after = """
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class A {
@Test
public void test() {
String str = "string";
assertThat(str).isSameAs(notification());
assertThat(str).as("Should be the same").isSameAs(notification());
assertThat(str).withFailMessage(() -> "Should be the same").isSameAs(notification());
}
private String notification() {
return "String";
}
}
"""
)

@Test
fun mixedReferences() = assertRefactored(
before = """
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertSame;
public class A {
@Test
public void test() {
String str = "string";
assertSame(notification(), str);
org.junit.jupiter.api.Assertions.assertSame(notification(), str, "Should be the same");
assertSame(notification(), str, () -> "Should be the same");
}
private String notification() {
return "String";
}
}
""",
after = """
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
public class A {
@Test
public void test() {
String str = "string";
assertThat(str).isSameAs(notification());
assertThat(str).as("Should be the same").isSameAs(notification());
assertThat(str).withFailMessage(() -> "Should be the same").isSameAs(notification());
}
private String notification() {
return "String";
}
}
"""
)
}

0 comments on commit 5ad7208

Please sign in to comment.