diff --git a/src/main/java/org/openrewrite/java/testing/junit5/JUnitSoftAssertionsToSoftAssertionsExtension.java b/src/main/java/org/openrewrite/java/testing/junit5/JUnitSoftAssertionsToSoftAssertionsExtension.java
new file mode 100644
index 000000000..962be1ec5
--- /dev/null
+++ b/src/main/java/org/openrewrite/java/testing/junit5/JUnitSoftAssertionsToSoftAssertionsExtension.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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 lombok.Getter;
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.Cursor;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Preconditions;
+import org.openrewrite.Recipe;
+import org.openrewrite.SourceFile;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.internal.ListUtils;
+import org.openrewrite.java.AnnotationMatcher;
+import org.openrewrite.java.ChangeType;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.java.JavaTemplate;
+import org.openrewrite.java.search.UsesType;
+import org.openrewrite.java.service.AnnotationService;
+import org.openrewrite.java.trait.Annotated;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.TypeUtils;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static java.lang.String.format;
+import static java.util.Collections.emptySet;
+import static java.util.Comparator.comparing;
+
+public class JUnitSoftAssertionsToSoftAssertionsExtension extends Recipe {
+
+ private static final String JUNIT_SOFT_ASSERTIONS = "org.assertj.core.api.JUnitSoftAssertions";
+ private static final String JUNIT_BDD_SOFT_ASSERTIONS = "org.assertj.core.api.JUnitBDDSoftAssertions";
+
+ private static final Map RULE_TO_PROVIDER = new LinkedHashMap() {{
+ put(JUNIT_SOFT_ASSERTIONS, "org.assertj.core.api.SoftAssertions");
+ put(JUNIT_BDD_SOFT_ASSERTIONS, "org.assertj.core.api.BDDSoftAssertions");
+ }};
+
+ private static final String RULE = "org.junit.Rule";
+ private static final String EXTEND_WITH = "org.junit.jupiter.api.extension.ExtendWith";
+ private static final String INJECT_SOFT_ASSERTIONS = "org.assertj.core.api.junit.jupiter.InjectSoftAssertions";
+ private static final String SOFT_ASSERTIONS_EXTENSION = "org.assertj.core.api.junit.jupiter.SoftAssertionsExtension";
+
+ private static final AnnotationMatcher RULE_MATCHER = new AnnotationMatcher('@' + RULE);
+ private static final AnnotationMatcher EXTEND_WITH_MATCHER =
+ new AnnotationMatcher(format("@%s(%s.class)", EXTEND_WITH, SOFT_ASSERTIONS_EXTENSION), true);
+
+ private static final String CONVERTED_TYPES = "convertedSoftAssertionsRuleTypes";
+ private static final String UNCONVERTIBLE_TYPES = "unconvertibleSoftAssertionsRuleTypes";
+
+ @Getter
+ final String displayName = "AssertJ `@Rule` soft assertions to `SoftAssertionsExtension`";
+
+ @Getter
+ final String description = "Replaces `@Rule` fields of type `JUnitSoftAssertions` or `JUnitBDDSoftAssertions` with " +
+ "`@InjectSoftAssertions` fields, and registers `@ExtendWith(SoftAssertionsExtension.class)` on the test class. " +
+ "JUnit Jupiter does not run JUnit 4 rules, so soft assertions collected through such a rule would otherwise " +
+ "never be reported, silently passing tests that ought to fail.";
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ return Preconditions.check(
+ Preconditions.and(
+ new UsesType<>(RULE, false),
+ Preconditions.or(
+ new UsesType<>(JUNIT_SOFT_ASSERTIONS, false),
+ new UsesType<>(JUNIT_BDD_SOFT_ASSERTIONS, false))),
+ new JavaIsoVisitor() {
+
+ // Kotlin properties would need `lateinit var` rather than a dropped `final` and initializer, and
+ // the `ChangeType` below is driven from a `J.CompilationUnit` that a Kotlin source does not have.
+ @Override
+ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) {
+ return sourceFile instanceof J.CompilationUnit;
+ }
+
+ @Override
+ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
+ getCursor().putMessage(UNCONVERTIBLE_TYPES, unconvertibleRuleTypes(cu));
+ J.CompilationUnit c = super.visitCompilationUnit(cu, ctx);
+ Set convertedTypes = getCursor().pollMessage(CONVERTED_TYPES);
+ if (convertedTypes == null) {
+ return c;
+ }
+ for (String convertedType : convertedTypes) {
+ c = (J.CompilationUnit) new ChangeType(convertedType, RULE_TO_PROVIDER.get(convertedType), true)
+ .getVisitor().visitNonNull(c, ctx);
+ }
+ maybeRemoveImport(RULE);
+ return c;
+ }
+
+ @Override
+ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
+ J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);
+ if (getCursor().pollMessage(CONVERTED_TYPES) == null ||
+ service(AnnotationService.class).matches(updateCursor(cd), EXTEND_WITH_MATCHER)) {
+ return cd;
+ }
+ maybeAddImport(EXTEND_WITH);
+ maybeAddImport(SOFT_ASSERTIONS_EXTENSION);
+ return JavaTemplate.builder("@ExtendWith(SoftAssertionsExtension.class)")
+ .imports(EXTEND_WITH, SOFT_ASSERTIONS_EXTENSION)
+ .javaParser(JavaParser.fromJavaVersion()
+ .classpathFromResources(ctx, "junit-jupiter-api-5", "assertj-core-3"))
+ .build()
+ .apply(updateCursor(cd), cd.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName)));
+ }
+
+ @Override
+ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) {
+ J.VariableDeclarations mv = super.visitVariableDeclarations(multiVariable, ctx);
+ String ruleType = softAssertionsType(mv);
+ if (ruleType == null ||
+ J.Modifier.hasModifier(mv.getModifiers(), J.Modifier.Type.Static) ||
+ getCursor().>getNearestMessage(UNCONVERTIBLE_TYPES, emptySet()).contains(ruleType) ||
+ !service(AnnotationService.class).matches(getCursor(), RULE_MATCHER)) {
+ return mv;
+ }
+
+ mv = maybeAutoFormat(mv, mv.withModifiers(ListUtils.map(mv.getModifiers(),
+ m -> m.getType() == J.Modifier.Type.Final ? null : m)), ctx, getCursor().getParentOrThrow());
+ mv = mv.withVariables(ListUtils.map(mv.getVariables(), v -> v.withInitializer(null)));
+ mv = (J.VariableDeclarations) new Annotated.Matcher('@' + RULE)
+ .asVisitor(a -> JavaTemplate.builder("@InjectSoftAssertions")
+ .imports(INJECT_SOFT_ASSERTIONS)
+ .javaParser(JavaParser.fromJavaVersion()
+ .classpathFromResources(ctx, "junit-jupiter-api-5", "assertj-core-3"))
+ .build()
+ .apply(a.getCursor(), a.getTree().getCoordinates().replace()))
+ .visitNonNull(mv, ctx, getCursor().getParentOrThrow());
+ maybeAddImport(INJECT_SOFT_ASSERTIONS);
+
+ getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, CONVERTED_TYPES, ruleType);
+ getCursor().dropParentUntil(J.CompilationUnit.class::isInstance)
+ .>computeMessageIfAbsent(CONVERTED_TYPES, k -> new LinkedHashSet<>())
+ .add(ruleType);
+ return mv;
+ }
+ });
+ }
+
+ private static @Nullable String softAssertionsType(J.VariableDeclarations mv) {
+ for (String ruleType : RULE_TO_PROVIDER.keySet()) {
+ if (TypeUtils.isOfClassType(mv.getType(), ruleType)) {
+ return ruleType;
+ }
+ }
+ return null;
+ }
+
+ // Types also declared as a field this recipe leaves alone, such as a `@ClassRule` or a field only referenced from a
+ // `RuleChain`; those fields still have to implement `TestRule`, so the compilation unit wide `ChangeType` that
+ // follows the conversion must not run for their type.
+ private static Set unconvertibleRuleTypes(J.CompilationUnit cu) {
+ return new JavaIsoVisitor>() {
+ @Override
+ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations mv, Set types) {
+ String ruleType = softAssertionsType(mv);
+ if (ruleType != null && isField(getCursor()) &&
+ (J.Modifier.hasModifier(mv.getModifiers(), J.Modifier.Type.Static) ||
+ mv.getLeadingAnnotations().stream().noneMatch(RULE_MATCHER::matches))) {
+ types.add(ruleType);
+ }
+ return super.visitVariableDeclarations(mv, types);
+ }
+
+ private boolean isField(Cursor cursor) {
+ return cursor.getParentTreeCursor().getParentTreeCursor().getValue() instanceof J.ClassDeclaration;
+ }
+ }.reduce(cu, new LinkedHashSet<>());
+ }
+}
diff --git a/src/main/resources/META-INF/rewrite/junit5.yml b/src/main/resources/META-INF/rewrite/junit5.yml
index cefd2bf66..ea8e5c8d9 100755
--- a/src/main/resources/META-INF/rewrite/junit5.yml
+++ b/src/main/resources/META-INF/rewrite/junit5.yml
@@ -139,6 +139,7 @@ recipeList:
- org.openrewrite.java.testing.arquillian.ArquillianJUnit4ToArquillianJUnit5
- org.openrewrite.java.testing.byteman.BytemanJUnit4ToBytemanJUnit5
- org.openrewrite.java.testing.dbrider.MigrateDbRiderSpringToDbRiderJUnit5
+ - org.openrewrite.java.testing.junit5.JUnitSoftAssertionsToSoftAssertionsExtension
# Convert any leftover ExternalResource rules last, after more specific rules have already run
- org.openrewrite.java.testing.junit5.HandleExternalResourceRules
---
diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv
index 103d4aaff..a22bab8aa 100644
--- a/src/main/resources/META-INF/rewrite/recipes.csv
+++ b/src/main/resources/META-INF/rewrite/recipes.csv
@@ -47,7 +47,7 @@ maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.tes
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.AssertJShortRulesRecipes$AbstractShortAssertIsOneRecipe,Replace `isEqualTo(1)` with `isOne()`,Replace `isEqualTo(1)` with `isOne()`.,1,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.AssertJShortRulesRecipes$AbstractShortAssertIsZeroRecipe,Replace `isEqualTo(0)` with `isZero()`,Replace `isEqualTo(0)` with `isZero()`.,1,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.AssertJShortRulesRecipes,Adopt AssertJ Short Assertions,Adopt AssertJ Short Assertions. Favor semantically explicit methods (e.g. `myShort.isZero()` over `myShort.isEqualTo(0)`).,6,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.Assertj,AssertJ best practices,Migrates JUnit asserts to AssertJ and applies best practices to assertions.,908,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.Assertj,AssertJ best practices,Migrates JUnit asserts to AssertJ and applies best practices to assertions.,911,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.CollapseConsecutiveAssertThatStatements,Collapse consecutive `assertThat` statements,Collapse consecutive `assertThat` statements into single `assertThat` chained statement. This recipe ignores `assertThat` statements that have method invocation as parameter.,1,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.DecomposeConjunctionAssertion,Decompose `assertThat` on conjunctions into separate assertions,"Split `assertThat(a && b).isTrue()` into separate `assertThat(a).isTrue()` and `assertThat(b).isTrue()` statements, so each condition is asserted (and reported) on its own. This lets the dedicated assertion recipes simplify each conjunct, and `CollapseConsecutiveAssertThatStatements` fuse them back into a single chain when the actual is a plain expression. Only the direct `assertThat(...).isTrue()` form is decomposed; `isFalse()` is left alone, as negating a conjunction is not equivalent to negating each conjunct.",1,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.FestToAssertj,Migrate Fest 2.x to AssertJ,"AssertJ provides a rich set of assertions, truly helpful error messages, improves test code readability. Converts Fest 2.x imports to AssertJ imports.",10,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,
@@ -63,7 +63,7 @@ maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.tes
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.JUnitAssertThrowsToAssertExceptionType,JUnit AssertThrows to AssertJ exceptionType,Convert `JUnit#AssertThrows` to `AssertJ#assertThatExceptionOfType` to allow for chained assertions on the thrown exception.,1,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.JUnitAssertTrueToAssertThat,JUnit `assertTrue` to AssertJ,Convert JUnit-style `assertTrue()` to AssertJ's `assertThat().isTrue()`.,1,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.JUnitFailToAssertJFail,JUnit fail to AssertJ,Convert JUnit-style `fail()` to AssertJ's `fail()`.,1,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.JUnitToAssertj,Migrate JUnit asserts to AssertJ,"AssertJ provides a rich set of assertions, truly helpful error messages, improves test code readability. Converts assertions from `org.junit.jupiter.api.Assertions` to `org.assertj.core.api.Assertions`. Will convert JUnit 4 to JUnit Jupiter if necessary to match and modify assertions.",341,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.JUnitToAssertj,Migrate JUnit asserts to AssertJ,"AssertJ provides a rich set of assertions, truly helpful error messages, improves test code readability. Converts assertions from `org.junit.jupiter.api.Assertions` to `org.assertj.core.api.Assertions`. Will convert JUnit 4 to JUnit Jupiter if necessary to match and modify assertions.",344,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.JUnitTryFailToAssertThatThrownBy,Convert try-catch-fail blocks to AssertJ's assertThatThrownBy,"Replace try-catch blocks where the try block ends with a `fail()` statement and the catch block optionally contains assertions, with AssertJ's `assertThatThrownBy()`.",1,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.MigrateAssertionsForClassAndInterfaceTypes,Migrate `AssertionsForClassTypes` and `AssertionsForInterfaceTypes` to `Assertions`,"AssertJ deprecated `AssertionsForClassTypes` and `AssertionsForInterfaceTypes` in favor of the unified `Assertions` entry point. This recipe retargets their static methods to `Assertions`, using `assertThatObject` where a plain `assertThat` would otherwise re-bind to a more specific overload and stop compiling (see https://github.com/openrewrite/rewrite-testing-frameworks/issues/664).",4,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.assertj.MigrateAssertionsForClassTypes,Use `Assertions.assertThatObject` for ambiguous `AssertionsForClassTypes.assertThat` calls,"The deprecated `AssertionsForClassTypes.assertThat(T)` always returns an `ObjectAssert`, while the unified `Assertions.assertThat` additionally offers more specific overloads (e.g. for `Iterable`, `Map`, `Predicate`). For arguments matching those overloads, rename `assertThat` to `assertThatObject` so that migrating to `Assertions` keeps returning an `ObjectAssert` and the code keeps compiling.",1,AssertJ,Testing,Java,,,Basic building blocks for transforming Java code.,,
@@ -109,7 +109,7 @@ maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.tes
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.datafaker.JavaFakerToDataFaker,Migrate from Java Faker to Datafaker,Change imports and dependencies related to Java Faker to Datafaker replacements.,6,DataFaker,Testing,Java,Recipes for migrating from JavaFaker to [DataFaker](https://www.datafaker.net/).,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.dbrider.ExecutionListenerToDbRiderAnnotation,Migrate the `DBRiderTestExecutionListener` to the `@DBRider` annotation,Migrate the `DBRiderTestExecutionListener` to the `@DBRider` annotation. This recipe is useful when migrating from JUnit 4 `dbrider-spring` to JUnit 5 `dbrider-junit5`.,1,DBRider,Testing,Java,Recipes for [DBRider](https://database-rider.github.io/database-rider/) database testing framework.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.dbrider.MigrateDbRiderSpringToDbRiderJUnit5,Migrate rider-spring (JUnit4) to rider-junit5 (JUnit5),This recipe will migrate the necessary dependencies and annotations from DbRider with JUnit4 to JUnit5 in a Spring application.,3,DBRider,Testing,Java,Recipes for [DBRider](https://database-rider.github.io/database-rider/) database testing framework.,,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.easymock.EasyMockToMockito,Migrate from EasyMock to Mockito,This recipe will apply changes commonly needed when migrating from EasyMock to Mockito.,133,EasyMock,Testing,Java,Recipes for migrating from [EasyMock](https://easymock.org/) to Mockito.,,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.easymock.EasyMockToMockito,Migrate from EasyMock to Mockito,This recipe will apply changes commonly needed when migrating from EasyMock to Mockito.,135,EasyMock,Testing,Java,Recipes for migrating from [EasyMock](https://easymock.org/) to Mockito.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.easymock.EasyMockVerifyToMockitoVerify,Replace EasyMock `verify` calls with Mockito `verify` calls,Replace `EasyMock.verify(dependency)` with individual `Mockito.verify(dependency).method()` calls based on expected methods.,1,EasyMock,Testing,Java,Recipes for migrating from [EasyMock](https://easymock.org/) to Mockito.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.easymock.RemoveExtendsEasyMockSupport,Migrate Test classes that extend `org.easymock.EasyMockSupport` to use Mockito,Modify test classes by removing extends EasyMockSupport and replacing EasyMock methods with Mockito equivalents.,1,EasyMock,Testing,Java,Recipes for migrating from [EasyMock](https://easymock.org/) to Mockito.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.hamcrest.AddHamcrestIfUsed,Add `org.hamcrest:hamcrest` if it is used,"JUnit Jupiter does not include hamcrest as a transitive dependency. If needed, add a direct dependency.",2,Hamcrest,Testing,Java,Recipes for migrating from [Hamcrest](http://hamcrest.org/) matchers to AssertJ.,,Basic building blocks for transforming Java code.,,
@@ -130,7 +130,7 @@ maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.tes
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.jmockit.JMockitAnnotatedArgumentToMockito,Convert JMockit `@Mocked` and `@Injectable` annotated arguments,Convert JMockit `@Mocked` and `@Injectable` annotated arguments into Mockito statements.,1,JMockit,Testing,Java,Recipes for migrating from [JMockit](https://jmockit.github.io/) to Mockito.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.jmockit.JMockitBlockToMockito,"Rewrite JMockit Expectations, NonStrictExpectations, Verifications, VerificationsInOrder, FullVerifications","Rewrites JMockit `Expectations, NonStrictExpectations, Verifications, VerificationsInOrder, FullVerifications` blocks to Mockito statements.",1,JMockit,Testing,Java,Recipes for migrating from [JMockit](https://jmockit.github.io/) to Mockito.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.jmockit.JMockitMockUpToMockito,Rewrite JMockit MockUp to Mockito statements,Rewrites JMockit `MockUp` blocks to Mockito statements. This recipe will not rewrite private methods in MockUp.,1,JMockit,Testing,Java,Recipes for migrating from [JMockit](https://jmockit.github.io/) to Mockito.,,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.jmockit.JMockitToMockito,Migrate from JMockit to Mockito,This recipe will apply changes commonly needed when migrating from JMockit to Mockito.,101,JMockit,Testing,Java,Recipes for migrating from [JMockit](https://jmockit.github.io/) to Mockito.,,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.jmockit.JMockitToMockito,Migrate from JMockit to Mockito,This recipe will apply changes commonly needed when migrating from JMockit to Mockito.,103,JMockit,Testing,Java,Recipes for migrating from [JMockit](https://jmockit.github.io/) to Mockito.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit.JUnit6BestPractices,JUnit 6 best practices,Applies best practices to tests.,107,JUnit,Testing,Java,Best practices that apply across [JUnit](https://junit.org/) versions.,,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit.JupiterBestPractices,JUnit Jupiter best practices,Applies best practices to tests.,48,JUnit,Testing,Java,Best practices that apply across [JUnit](https://junit.org/) versions.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit.RemoveJupiterMigrationSupport,Remove JUnit Jupiter migrationsupport,Remove JUnit Jupiter migrationsupport.,3,JUnit,Testing,Java,Best practices that apply across [JUnit](https://junit.org/) versions.,,Basic building blocks for transforming Java code.,,
@@ -158,9 +158,10 @@ maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.tes
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.HandleExternalResourceRules,Handle the usage of ExternalResourceRule fields using @ExtendWith(ExternalResourceSupport.class),Handles the usage of the ExternalResourceRule fields by adding the @ExtendWith(ExternalResourceSupport.class) annotation to the test class.,1,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.IgnoreToDisabled,Use JUnit Jupiter `@Disabled`,Migrates JUnit 4.x `@Ignore` to JUnit Jupiter `@Disabled`.,2,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.ImplausibleTimeoutToMinutes,Make implausibly long `@Timeout` values explicit in minutes,"JUnit Jupiter's `@Timeout` defaults to `TimeUnit.SECONDS`, so a value such as `@Timeout(10000)` is interpreted as almost three hours, which is most likely a mistake where milliseconds were intended. This recipe rewrites such implausibly large second-based timeouts to the equivalent number of minutes, for instance `@Timeout(value = 167, unit = TimeUnit.MINUTES)`, preserving the original (likely erroneous) semantics while making the mistake far more visible for review.",1,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,"[{""name"":""thresholdSeconds"",""type"":""Integer"",""displayName"":""Threshold in seconds"",""description"":""Timeouts of at least this many seconds (when the time unit is the default `SECONDS`) are considered implausibly long and are rewritten to the equivalent number of minutes. Defaults to `1000` seconds, about 17 minutes."",""example"":""1000""}]",
-maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.JUnit4to5Migration,JUnit Jupiter migration from JUnit 4.x,Migrates JUnit 4.x tests to JUnit Jupiter.,195,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
-maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.JUnit5BestPractices,JUnit 5 best practices,Applies best practices to tests.,268,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.JUnit4to5Migration,JUnit Jupiter migration from JUnit 4.x,Migrates JUnit 4.x tests to JUnit Jupiter.,198,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.JUnit5BestPractices,JUnit 5 best practices,Applies best practices to tests.,271,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]"
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.JUnitParamsRunnerToParameterized,Pragmatists `@RunWith(JUnitParamsRunner.class)` to JUnit Jupiter `@Parameterized` tests,Convert Pragmatists Parameterized test to the JUnit Jupiter ParameterizedTest equivalent.,1,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.JUnitSoftAssertionsToSoftAssertionsExtension,AssertJ `@Rule` soft assertions to `SoftAssertionsExtension`,"Replaces `@Rule` fields of type `JUnitSoftAssertions` or `JUnitBDDSoftAssertions` with `@InjectSoftAssertions` fields, and registers `@ExtendWith(SoftAssertionsExtension.class)` on the test class. JUnit Jupiter does not run JUnit 4 rules, so soft assertions collected through such a rule would otherwise never be reported, silently passing tests that ought to fail.",1,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.LifecycleNonPrivate,Make lifecycle methods non private,"Make JUnit 5's `@AfterAll`, `@AfterEach`, `@BeforeAll` and `@BeforeEach` non private.",1,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.MigrateAssertionFailedError,Migrate JUnit 4 assertion failure exceptions to JUnit Jupiter,Replace JUnit 4's `junit.framework.AssertionFailedError` and `org.junit.ComparisonFailure` with JUnit Jupiter's `org.opentest4j.AssertionFailedError`.,3,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.MigrateAssumptions,Use `Assertions#assume*(..)` and Hamcrest's `MatcherAssume#assume*(..)`,Many of JUnit 4's `Assume#assume(..)` methods have no direct counterpart in JUnit 5 and require Hamcrest JUnit's `MatcherAssume`.,7,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
@@ -188,7 +189,7 @@ maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.tes
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.UpgradeToJUnit514,Upgrade to JUnit 5.14,Upgrades JUnit 5 to 5.14.x and migrates all deprecated APIs.,24,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.UseAssertSame,Use JUnit5's `assertSame` or `assertNotSame` instead of `assertTrue(... == ...)`,Prefers the usage of `assertSame` or `assertNotSame` methods instead of using of vanilla `assertTrue` or `assertFalse` with a boolean comparison. Only applies when both operands are reference types — primitive operands are handled by `AssertTrueComparisonToAssertEquals`.,1,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.UseHamcrestAssertThat,Use `MatcherAssert#assertThat(..)`,JUnit 4's `Assert#assertThat(..)` This method was deprecated in JUnit 4 and removed in JUnit Jupiter.,3,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.UseMockitoExtension,Use Mockito JUnit Jupiter extension,Migrate uses of `@RunWith(MockitoJUnitRunner.class)` (and similar annotations) to `@ExtendWith(MockitoExtension.class)`.,78,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.UseMockitoExtension,Use Mockito JUnit Jupiter extension,Migrate uses of `@RunWith(MockitoJUnitRunner.class)` (and similar annotations) to `@ExtendWith(MockitoExtension.class)`.,80,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.UseTestMethodOrder,Migrate from JUnit 4 `@FixedMethodOrder` to JUnit 5 `@TestMethodOrder`,JUnit optionally allows test method execution order to be specified. This replaces JUnit 4 test execution ordering annotations with JUnit 5 replacements.,1,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.UseWiremockExtension,Use wiremock extension,"As of 2.31.0, wiremock [supports JUnit 5](https://wiremock.org/docs/junit-jupiter/) via an extension.",2,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.junit5.UseXMLUnitLegacy,Use XMLUnit Legacy for JUnit 5,Migrates XMLUnit 1.x to XMLUnit legacy 2.x.,2,JUnit Jupiter,Testing,Java,Best practices for JUnit Jupiter tests.,,Basic building blocks for transforming Java code.,,
@@ -213,11 +214,11 @@ maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.tes
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.CloseUnclosedStaticMocks,Close unclosed static mocks,"Ensures that all `mockStatic` calls are properly closed. If `mockStatic` is in lifecycle methods like `@BeforeEach` or `@BeforeAll`, creates a class variable and closes it in `@AfterEach` or `@AfterAll`. If `mockStatic` is inside a test method, wraps it in a try-with-resources block.",1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.MockConstructionToTryWithResources,Wrap `MockedConstruction` in try-with-resources,"Wraps `MockedConstruction` variable declarations that have explicit `.close()` calls into try-with-resources blocks, removing the explicit close call. This ensures proper resource management and makes the code cleaner.",1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.MockUtilsToStatic,Use static form of Mockito `MockUtil`,Best-effort attempt to remove Mockito `MockUtil` instances.,1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.Mockito1to3Migration,Mockito 3.x migration from 1.x,Upgrade Mockito from 1.x to 3.x.,69,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.Mockito1to4Migration,Mockito 4.x upgrade,Upgrade Mockito from 1.x to 4.x.,75,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.Mockito1to5Migration,Mockito 5.x upgrade,Upgrade Mockito from 1.x to 5.x.,81,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.Mockito4to5Only,Mockito 4 to 5.x upgrade only,Upgrade Mockito from 4.x to 5.x. Does not include 1.x to 4.x migration.,80,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
-maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.MockitoBestPractices,Mockito best practices,Applies best practices for Mockito tests.,87,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.Mockito1to3Migration,Mockito 3.x migration from 1.x,Upgrade Mockito from 1.x to 3.x.,71,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.Mockito1to4Migration,Mockito 4.x upgrade,Upgrade Mockito from 1.x to 4.x.,77,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.Mockito1to5Migration,Mockito 5.x upgrade,Upgrade Mockito from 1.x to 5.x.,83,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.Mockito4to5Only,Mockito 4 to 5.x upgrade only,Upgrade Mockito from 4.x to 5.x. Does not include 1.x to 4.x migration.,82,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
+maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.MockitoBestPractices,Mockito best practices,Applies best practices for Mockito tests.,89,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.MockitoJUnitRunnerSilentToExtension,JUnit 4 MockitoJUnitRunner.Silent to JUnit Jupiter MockitoExtension with LENIENT settings,Replace `@RunWith(MockitoJUnitRunner.Silent.class)` with `@ExtendWith(MockitoExtension.class)` and `@MockitoSettings(strictness = Strictness.LENIENT)`.,1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.MockitoJUnitRunnerToExtension,Replace JUnit 4 MockitoJUnitRunner with junit-jupiter MockitoExtension,"Replace JUnit 4 MockitoJUnitRunner annotations with JUnit 5 `@ExtendWith(MockitoExtension.class)` using the appropriate strictness levels (LENIENT, WARN, STRICT_STUBS).",1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.MockitoWhenOnStaticToMockStatic,Replace `Mockito.when` on static (non mock) with try-with-resource with MockedStatic,"Replace `Mockito.when` on static (non mock) with try-with-resource with MockedStatic as Mockito4 no longer allows this. For JUnit 4/5 & TestNG: When `@Before*` is used, a `close` call is added to the corresponding `@After*` method. This change moves away from implicit bytecode manipulation for static method stubbing, making mocking behavior more explicit and scoped to avoid unintended side effects.",1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,,
diff --git a/src/test/java/org/openrewrite/java/testing/junit5/JUnit5MigrationTest.java b/src/test/java/org/openrewrite/java/testing/junit5/JUnit5MigrationTest.java
index 2035a1cbb..879d967dd 100644
--- a/src/test/java/org/openrewrite/java/testing/junit5/JUnit5MigrationTest.java
+++ b/src/test/java/org/openrewrite/java/testing/junit5/JUnit5MigrationTest.java
@@ -1086,4 +1086,50 @@ public void testAdd() {
)
);
}
+
+ @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/1097")
+ @Test
+ void junitSoftAssertionsRuleToSoftAssertionsExtension() {
+ rewriteRun(
+ spec -> spec
+ .parser(JavaParser.fromJavaVersion()
+ .classpathFromResources(new InMemoryExecutionContext(), "junit-4", "assertj-core-3")),
+ //language=java
+ java(
+ """
+ import org.assertj.core.api.JUnitSoftAssertions;
+ import org.junit.Rule;
+ import org.junit.Test;
+
+ public class SoftlyTest {
+ @Rule
+ public final JUnitSoftAssertions softly = new JUnitSoftAssertions();
+
+ @Test
+ public void multipleAssertions() {
+ softly.assertThat("foo").isEqualTo("bar");
+ }
+ }
+ """,
+ """
+ import org.assertj.core.api.SoftAssertions;
+ import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
+ import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+ import org.junit.jupiter.api.Test;
+ import org.junit.jupiter.api.extension.ExtendWith;
+
+ @ExtendWith(SoftAssertionsExtension.class)
+ public class SoftlyTest {
+ @InjectSoftAssertions
+ public SoftAssertions softly;
+
+ @Test
+ public void multipleAssertions() {
+ softly.assertThat("foo").isEqualTo("bar");
+ }
+ }
+ """
+ )
+ );
+ }
}
diff --git a/src/test/java/org/openrewrite/java/testing/junit5/JUnitSoftAssertionsToSoftAssertionsExtensionTest.java b/src/test/java/org/openrewrite/java/testing/junit5/JUnitSoftAssertionsToSoftAssertionsExtensionTest.java
new file mode 100644
index 000000000..730879bdf
--- /dev/null
+++ b/src/test/java/org/openrewrite/java/testing/junit5/JUnitSoftAssertionsToSoftAssertionsExtensionTest.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright 2026 the original author or authors.
+ *
+ * Licensed under the Moderne Source Available License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://docs.moderne.io/licensing/moderne-source-available-license
+ *
+ * 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.DocumentExample;
+import org.openrewrite.InMemoryExecutionContext;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.kotlin.KotlinParser;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+import static org.openrewrite.kotlin.Assertions.kotlin;
+
+class JUnitSoftAssertionsToSoftAssertionsExtensionTest implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipe(new JUnitSoftAssertionsToSoftAssertionsExtension())
+ .parser(JavaParser.fromJavaVersion()
+ .classpathFromResources(new InMemoryExecutionContext(),
+ "junit-4", "junit-jupiter-api-5", "assertj-core-3"));
+ }
+
+ @DocumentExample
+ @Test
+ void softAssertionsRule() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import org.assertj.core.api.JUnitSoftAssertions;
+ import org.junit.Rule;
+ import org.junit.Test;
+
+ public class SoftlyTest {
+ @Rule
+ public final JUnitSoftAssertions softly = new JUnitSoftAssertions();
+
+ @Test
+ public void multipleAssertions() {
+ softly.assertThat("foo").isEqualTo("bar");
+ }
+ }
+ """,
+ """
+ import org.assertj.core.api.SoftAssertions;
+ import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
+ import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+ import org.junit.Test;
+ import org.junit.jupiter.api.extension.ExtendWith;
+
+ @ExtendWith(SoftAssertionsExtension.class)
+ public class SoftlyTest {
+ @InjectSoftAssertions
+ public SoftAssertions softly;
+
+ @Test
+ public void multipleAssertions() {
+ softly.assertThat("foo").isEqualTo("bar");
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void bddSoftAssertionsRule() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import org.assertj.core.api.JUnitBDDSoftAssertions;
+ import org.junit.Rule;
+
+ public class SoftlyTest {
+ @Rule
+ public final JUnitBDDSoftAssertions softly = new JUnitBDDSoftAssertions();
+ }
+ """,
+ """
+ import org.assertj.core.api.BDDSoftAssertions;
+ import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
+ import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+ import org.junit.jupiter.api.extension.ExtendWith;
+
+ @ExtendWith(SoftAssertionsExtension.class)
+ public class SoftlyTest {
+ @InjectSoftAssertions
+ public BDDSoftAssertions softly;
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void ruleOnSameLineWithoutAccessModifier() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import org.assertj.core.api.JUnitSoftAssertions;
+ import org.junit.Rule;
+
+ class SoftlyTest {
+ @Rule
+ final JUnitSoftAssertions softly = new JUnitSoftAssertions();
+ }
+ """,
+ """
+ import org.assertj.core.api.SoftAssertions;
+ import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
+ import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+ import org.junit.jupiter.api.extension.ExtendWith;
+
+ @ExtendWith(SoftAssertionsExtension.class)
+ class SoftlyTest {
+ @InjectSoftAssertions
+ SoftAssertions softly;
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void retainExistingExtendWith() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import org.assertj.core.api.JUnitSoftAssertions;
+ import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+ import org.junit.Rule;
+ import org.junit.jupiter.api.extension.ExtendWith;
+
+ @ExtendWith(SoftAssertionsExtension.class)
+ class SoftlyTest {
+ @Rule
+ public JUnitSoftAssertions softly = new JUnitSoftAssertions();
+ }
+ """,
+ """
+ import org.assertj.core.api.SoftAssertions;
+ import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
+ import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+ import org.junit.jupiter.api.extension.ExtendWith;
+
+ @ExtendWith(SoftAssertionsExtension.class)
+ class SoftlyTest {
+ @InjectSoftAssertions
+ public SoftAssertions softly;
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void nestedClass() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import org.assertj.core.api.JUnitSoftAssertions;
+ import org.junit.Rule;
+ import org.junit.jupiter.api.Nested;
+
+ class SoftlyTest {
+ @Nested
+ class Inner {
+ @Rule
+ public final JUnitSoftAssertions softly = new JUnitSoftAssertions();
+ }
+ }
+ """,
+ """
+ import org.assertj.core.api.SoftAssertions;
+ import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
+ import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+ import org.junit.jupiter.api.Nested;
+ import org.junit.jupiter.api.extension.ExtendWith;
+
+ class SoftlyTest {
+ @ExtendWith(SoftAssertionsExtension.class)
+ @Nested
+ class Inner {
+ @InjectSoftAssertions
+ public SoftAssertions softly;
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void retainOtherRules() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import org.assertj.core.api.JUnitSoftAssertions;
+ import org.junit.Rule;
+ import org.junit.rules.TemporaryFolder;
+
+ class SoftlyTest {
+ @Rule
+ public final TemporaryFolder folder = new TemporaryFolder();
+
+ @Rule
+ public final JUnitSoftAssertions softly = new JUnitSoftAssertions();
+ }
+ """,
+ """
+ import org.assertj.core.api.SoftAssertions;
+ import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
+ import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+ import org.junit.Rule;
+ import org.junit.jupiter.api.extension.ExtendWith;
+ import org.junit.rules.TemporaryFolder;
+
+ @ExtendWith(SoftAssertionsExtension.class)
+ class SoftlyTest {
+ @Rule
+ public final TemporaryFolder folder = new TemporaryFolder();
+
+ @InjectSoftAssertions
+ public SoftAssertions softly;
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void noChangeForLocalSoftAssertions() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import org.assertj.core.api.JUnitSoftAssertions;
+ import org.junit.Rule;
+ import org.junit.rules.TemporaryFolder;
+
+ class SoftlyTest {
+ @Rule
+ public final TemporaryFolder folder = new TemporaryFolder();
+
+ void notATest() {
+ JUnitSoftAssertions softly = new JUnitSoftAssertions();
+ softly.assertThat("foo").isEqualTo("bar");
+ }
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void noChangeForClassRule() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import org.assertj.core.api.JUnitSoftAssertions;
+ import org.junit.ClassRule;
+
+ class SoftlyTest {
+ @ClassRule
+ public static final JUnitSoftAssertions softly = new JUnitSoftAssertions();
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void noChangeForClassRuleAlongsideRule() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import org.assertj.core.api.JUnitSoftAssertions;
+ import org.junit.ClassRule;
+ import org.junit.Rule;
+
+ class SoftlyTest {
+ @ClassRule
+ public static final JUnitSoftAssertions classSoftly = new JUnitSoftAssertions();
+
+ @Rule
+ public final JUnitSoftAssertions softly = new JUnitSoftAssertions();
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void noChangeForRuleChainMember() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import org.assertj.core.api.JUnitSoftAssertions;
+ import org.junit.Rule;
+ import org.junit.rules.RuleChain;
+
+ class SoftlyTest {
+ private final JUnitSoftAssertions chained = new JUnitSoftAssertions();
+
+ @Rule
+ public final JUnitSoftAssertions softly = new JUnitSoftAssertions();
+
+ @Rule
+ public final RuleChain chain = RuleChain.outerRule(chained);
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void convertOnlyTheRuleTypeWithoutRemainingRuleFields() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import org.assertj.core.api.JUnitBDDSoftAssertions;
+ import org.assertj.core.api.JUnitSoftAssertions;
+ import org.junit.ClassRule;
+ import org.junit.Rule;
+
+ class SoftlyTest {
+ @ClassRule
+ public static final JUnitSoftAssertions classSoftly = new JUnitSoftAssertions();
+
+ @Rule
+ public final JUnitBDDSoftAssertions softly = new JUnitBDDSoftAssertions();
+ }
+ """,
+ """
+ import org.assertj.core.api.BDDSoftAssertions;
+ import org.assertj.core.api.JUnitSoftAssertions;
+ import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
+ import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+ import org.junit.ClassRule;
+ import org.junit.jupiter.api.extension.ExtendWith;
+
+ @ExtendWith(SoftAssertionsExtension.class)
+ class SoftlyTest {
+ @ClassRule
+ public static final JUnitSoftAssertions classSoftly = new JUnitSoftAssertions();
+
+ @InjectSoftAssertions
+ public BDDSoftAssertions softly;
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void noChangeOnKotlin() {
+ rewriteRun(
+ spec -> spec.parser(KotlinParser.builder()
+ .classpathFromResources(new InMemoryExecutionContext(), "junit-4", "assertj-core-3")),
+ // language=kotlin
+ kotlin(
+ """
+ import org.assertj.core.api.JUnitSoftAssertions
+ import org.junit.Rule
+
+ class SoftlyTest {
+ @get:Rule
+ val softly: JUnitSoftAssertions = JUnitSoftAssertions()
+ }
+ """
+ )
+ );
+ }
+}