Skip to content

Commit 6aa80c9

Browse files
justine-gehringtimtebeekgithub-actions[bot]
authored
Feat find unit tests (#669)
* find unit tests * Add to datatable method invocations used in unit tests * Change accumulator unit test and its method invocations to a hasmap * polish * polish * polish * Apply formatter * Light polish * Use preconditions to tell tests and non tests apart * Apply correct license headers * Further polish * Loop over entrySet * Update src/main/java/org/openrewrite/java/testing/search/FindUnitTests.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: Tim te Beek <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent fcacbdd commit 6aa80c9

File tree

4 files changed

+314
-0
lines changed

4 files changed

+314
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.testing.search;
17+
18+
import lombok.Value;
19+
import org.openrewrite.Column;
20+
import org.openrewrite.DataTable;
21+
import org.openrewrite.Recipe;
22+
23+
public class FindUnitTestTable extends DataTable<FindUnitTestTable.Row> {
24+
public FindUnitTestTable(Recipe recipe) {
25+
super(recipe,
26+
recipe.getName(),
27+
recipe.getDescription());
28+
}
29+
30+
@Value
31+
public static class Row {
32+
@Column(displayName = "Full method name",
33+
description = "The fully qualified name of the method declaration")
34+
String fullyQualifiedMethodName;
35+
36+
@Column(displayName = "Method name",
37+
description = "The name of the method declaration")
38+
String methodName;
39+
40+
@Column(displayName = "Method invocation",
41+
description = "How the method declaration is used as method invocation in a unit test.")
42+
String methodInvocationExample;
43+
44+
@Column(displayName = "Name of test",
45+
description = "The name of the unit test where the method declaration is used.")
46+
String nameOfTest;
47+
48+
@Column(displayName = "Location of test",
49+
description = "The location of the unit test where the method declaration is used.")
50+
String locationOfTest;
51+
}
52+
}
53+
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.testing.search;
17+
18+
import lombok.Value;
19+
import org.openrewrite.ExecutionContext;
20+
import org.openrewrite.Preconditions;
21+
import org.openrewrite.ScanningRecipe;
22+
import org.openrewrite.TreeVisitor;
23+
import org.openrewrite.java.JavaVisitor;
24+
import org.openrewrite.java.search.IsLikelyNotTest;
25+
import org.openrewrite.java.search.IsLikelyTest;
26+
import org.openrewrite.java.tree.J;
27+
28+
import java.util.HashMap;
29+
import java.util.HashSet;
30+
import java.util.Map;
31+
import java.util.Set;
32+
33+
import static java.util.Collections.singletonList;
34+
35+
public class FindUnitTests extends ScanningRecipe<FindUnitTests.Accumulator> {
36+
37+
@Override
38+
public String getDisplayName() {
39+
return "Find unit tests";
40+
}
41+
42+
@Override
43+
public String getDescription() {
44+
return "Produces a data table showing examples of how methods declared get used in unit tests.";
45+
}
46+
47+
transient FindUnitTestTable unitTestTable = new FindUnitTestTable(this);
48+
49+
public static class Accumulator {
50+
Map<UnitTest, Set<J.MethodInvocation>> unitTestAndTheirMethods = new HashMap<>();
51+
}
52+
53+
@Override
54+
public Accumulator getInitialValue(ExecutionContext ctx) {
55+
return new Accumulator();
56+
}
57+
58+
@Override
59+
public TreeVisitor<?, ExecutionContext> getScanner(Accumulator acc) {
60+
JavaVisitor<ExecutionContext> scanningVisitor = new JavaVisitor<ExecutionContext>() {
61+
@Override
62+
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
63+
// get the method declaration the method invocation is in
64+
J.MethodDeclaration methodDeclaration = getCursor().firstEnclosing(J.MethodDeclaration.class);
65+
if (methodDeclaration != null &&
66+
methodDeclaration.getLeadingAnnotations().stream()
67+
.filter(o -> o.getAnnotationType() instanceof J.Identifier)
68+
.anyMatch(o -> "Test".equals(o.getSimpleName()))) {
69+
UnitTest unitTest = new UnitTest(
70+
getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class).getType().getFullyQualifiedName(),
71+
methodDeclaration.getSimpleName(),
72+
methodDeclaration.printTrimmed(getCursor()));
73+
acc.unitTestAndTheirMethods.merge(unitTest,
74+
new HashSet<>(singletonList(method)),
75+
(a, b) -> {
76+
a.addAll(b);
77+
return a;
78+
});
79+
}
80+
return super.visitMethodInvocation(method, ctx);
81+
}
82+
};
83+
return Preconditions.check(new IsLikelyTest().getVisitor(), scanningVisitor);
84+
}
85+
86+
@Override
87+
public TreeVisitor<?, ExecutionContext> getVisitor(Accumulator acc) {
88+
JavaVisitor<ExecutionContext> tableRowVisitor = new JavaVisitor<ExecutionContext>() {
89+
@Override
90+
public J visitMethodDeclaration(J.MethodDeclaration methodDeclaration, ExecutionContext ctx) {
91+
for (Map.Entry<UnitTest, Set<J.MethodInvocation>> entry : acc.unitTestAndTheirMethods.entrySet()) {
92+
for (J.MethodInvocation method : entry.getValue()) {
93+
if (method.getSimpleName().equals(methodDeclaration.getSimpleName())) {
94+
unitTestTable.insertRow(ctx, new FindUnitTestTable.Row(
95+
methodDeclaration.getName().toString(),
96+
methodDeclaration.getSimpleName(),
97+
method.printTrimmed(getCursor()),
98+
entry.getKey().getClazz(),
99+
entry.getKey().getUnitTestName()
100+
));
101+
}
102+
}
103+
}
104+
return super.visitMethodDeclaration(methodDeclaration, ctx);
105+
}
106+
};
107+
return Preconditions.check(new IsLikelyNotTest().getVisitor(), tableRowVisitor);
108+
}
109+
110+
}
111+
112+
@Value
113+
class UnitTest {
114+
String clazz;
115+
String unitTestName;
116+
String unitTest;
117+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
@NullMarked
17+
package org.openrewrite.java.testing.search;
18+
19+
import org.jspecify.annotations.NullMarked;
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.testing.search;
17+
18+
import org.intellij.lang.annotations.Language;
19+
import org.junit.jupiter.api.Nested;
20+
import org.junit.jupiter.api.Test;
21+
import org.openrewrite.DocumentExample;
22+
import org.openrewrite.InMemoryExecutionContext;
23+
import org.openrewrite.java.JavaParser;
24+
import org.openrewrite.test.RecipeSpec;
25+
import org.openrewrite.test.RewriteTest;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.openrewrite.java.Assertions.java;
29+
30+
class FindUnitTestsTest implements RewriteTest {
31+
32+
@Language("java")
33+
private static final String CLASS_FOO = """
34+
package foo;
35+
36+
public class Foo {
37+
public void bar() {
38+
}
39+
public void baz() {
40+
}
41+
}
42+
""";
43+
44+
@Override
45+
public void defaults(RecipeSpec spec) {
46+
spec.recipe(new FindUnitTests())
47+
.parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(),
48+
"junit-jupiter-api-5.9"));
49+
}
50+
51+
@DocumentExample
52+
@Test
53+
void dataTable() {
54+
rewriteRun(
55+
spec -> spec.dataTable(FindUnitTestTable.Row.class, rows -> assertThat(rows)
56+
.extracting(FindUnitTestTable.Row::getFullyQualifiedMethodName)
57+
.containsExactly("bar", "baz")),
58+
java(CLASS_FOO),
59+
//language=java
60+
java(
61+
"""
62+
import foo.Foo;
63+
import org.junit.jupiter.api.Test;
64+
65+
public class FooTest {
66+
@Test
67+
public void test() {
68+
Foo foo = new Foo();
69+
foo.bar();
70+
foo.baz();
71+
}
72+
}
73+
"""
74+
)
75+
);
76+
}
77+
78+
@Nested
79+
class NotFound {
80+
81+
@Test
82+
void notATest() {
83+
//language=java
84+
rewriteRun(
85+
spec -> spec.afterRecipe(run -> assertThat(run.getDataTables()).hasSize(1)), // stats table
86+
java(CLASS_FOO),
87+
java(
88+
"""
89+
import foo.Foo;
90+
91+
public class FooTest {
92+
public void test() {
93+
new Foo().bar();
94+
}
95+
}
96+
"""
97+
)
98+
);
99+
}
100+
101+
@Test
102+
void methodFromTest() {
103+
//language=java
104+
rewriteRun(
105+
spec -> spec.afterRecipe(run -> assertThat(run.getDataTables()).hasSize(1)), // stats table
106+
java(CLASS_FOO),
107+
java(
108+
"""
109+
import org.junit.jupiter.api.Test;
110+
111+
public class FooTest {
112+
@Test
113+
public void test() {
114+
beep();
115+
}
116+
117+
public void beep() {
118+
}
119+
}
120+
"""
121+
)
122+
);
123+
}
124+
}
125+
}

0 commit comments

Comments
 (0)