Skip to content

Migrate AssertJ @Rule soft assertions to SoftAssertionsExtension - #1105

Merged
timtebeek merged 3 commits into
mainfrom
tim/issue-1097
Aug 25, 2026
Merged

Migrate AssertJ @Rule soft assertions to SoftAssertionsExtension#1105
timtebeek merged 3 commits into
mainfrom
tim/issue-1097

Conversation

@timtebeek

@timtebeek timtebeek commented Aug 25, 2026

Copy link
Copy Markdown
Member

JUnitSoftAssertions and JUnitBDDSoftAssertions only report their collected failures from TestRule#apply, which JUnit Jupiter never calls. JUnit4to5Migration left those @Rule fields untouched, so where JUnit 4 survives on the classpath (transitively, or deliberately for Testcontainers) the migrated tests compile, go green, and assert nothing.

HandleExternalResourceRules doesn't cover them: both types implement SoftAssertionsRule, not ExternalResource.

This adds JUnitSoftAssertionsToSoftAssertionsExtension, wired into JUnit4to5Migration ahead of the ExternalResource catch-all. For an instance @Rule field of either type it:

  • swaps @Rule for @InjectSoftAssertions,
  • changes the type to SoftAssertions / BDDSoftAssertions,
  • drops final and the initializer, which SoftAssertionsExtension requires (it rejects static or final fields and instantiates the provider itself),
  • adds @ExtendWith(SoftAssertionsExtension.class) to the declaring class unless already present.
@Rule
public final JUnitSoftAssertions softly = new JUnitSoftAssertions();

becomes

@ExtendWith(SoftAssertionsExtension.class)
class SoftlyTest {
    @InjectSoftAssertions
    public SoftAssertions softly;
}

@ClassRule/static fields are left alone, as the extension can't inject those.

`JUnitSoftAssertions` and `JUnitBDDSoftAssertions` only report collected
failures from `TestRule#apply`, which JUnit Jupiter never calls. Left as is,
the migrated tests silently pass no matter what they assert.

Replace such `@Rule` fields with `@InjectSoftAssertions` fields of the
corresponding `SoftAssertions`/`BDDSoftAssertions` type, dropping `final` and
the initializer as the extension requires, and register
`@ExtendWith(SoftAssertionsExtension.class)` on the declaring class.

Fixes #1097
The compilation unit wide `ChangeType` retyped every reference, including
fields this recipe deliberately leaves alone: a `@ClassRule`, or a field only
reachable through a `RuleChain`. Those still have to implement `TestRule`, so
converting them broke compilation.

Scan the compilation unit up front for such fields and skip their type
entirely. Skipping is per type, so a `JUnitBDDSoftAssertions` rule still
migrates alongside a blocked `JUnitSoftAssertions` `@ClassRule`.

Also reuse the `ListUtils.map` plus `maybeAutoFormat` idiom from
`TempDirNonFinal` to drop the `final` modifier.
On a Kotlin property with an explicit type the recipe threw
`IllegalStateException: Expected to find a matching parent`, as the
compilation unit wide `ChangeType` is driven from a `J.CompilationUnit` that a
Kotlin source does not have. It only escaped notice because an inferred
property type leaves `J.VariableDeclarations#getType()` null.

Proper Kotlin support needs `lateinit var` rather than a dropped `final` and
initializer, so skip Kotlin sources for now.
@timtebeek
timtebeek merged commit 3df16af into main Aug 25, 2026
1 of 2 checks passed
@timtebeek
timtebeek deleted the tim/issue-1097 branch August 25, 2026 12:40
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

JUnit 4 to 5 migration leaves @Rule JUnitSoftAssertions behind, silently disabling the assertions

1 participant