Repository navigation
Recipe quality triage: remaining fixes, wiring and polish (A1–O8) #1211
Description
We triaged all recipes in this repo against our recipe review checklist: 218 Java recipe classes and 23 YAML files. This issue tracks what we found. We reproduced 27 of the behavioural bugs with a throwaway RewriteTest, and marked those PROBED.
Each item is self-contained, so you can pick one up independently or point an agent at it ("work on item D3 of this issue"). Please reference the item ID in your PR title or description, and tick the box once the PR is merged.
How to pick up an item
- Line numbers are against
mainat6c313e2eand may have drifted, so search by symbol if needed. - Paths are abbreviated:
M/=src/main/java/org/openrewrite/java/testing/T/=src/test/java/org/openrewrite/java/testing/Y/=src/main/resources/META-INF/rewrite/
- PROBED means a throwaway test reproduced the behaviour; the probe input is given and can become the regression test. Confirmed means someone traced the code path, or checked the dependency's bytecode, and the behaviour is certain. Suspected means it was inferred from reading. Write the probe test first; if it does not reproduce, close the item with a note.
- Prefer bailing out (making no change) over guessing when a recipe can't preserve semantics.
- Every fix comes with a test. Tests use realistic classpaths and avoid
TypeValidation.none(). Expected output must be correct, not just a record of today's behaviour. - Use one concern per commit, with an imperative headline that names APIs in backticks.
- Items in A need a maintainer decision first. Comment on the issue before starting.
- Already tracked elsewhere, so left out:
- Parameterized setup:
JUnit4to5MigrationmovesParameterizedparameter assignment after@BeforeEach, so setup methods read null #1125,ParameterizedRunnerToParameterizedsilently skips classes whose@Parametersmethod is inherited, leaving a partially migrated class that #1126, ParameterizedRunnerToParameterized: migrate classes whose @Parameters method is inherited #1139, ParameterizedRunnerToParameterized: run setup methods after the parameters are assigned #1169, Fix parameterized setup to run once after initialization #1190. TestCase(String)constructors:MigrateJUnitTestCaseremoves theTestCase(String)constructor without updating its callers, breaking compilation #1107, A retainedTestCase(String)constructor leaves the migrated test class unrunnable under JUnit 5 #1108, Fix dangling constructor calls when migrating JUnit TestCase #1119, Fix redundant JUnit TestCase constructors and their callers across source files. #1144, Fix retained JUnit TestCase constructors while preserving instance initialization. #1145.TestSetup: JUnit 4 to 5 migration discards the one-time setup aTestSetupdecorator performed, breaking tests at run time #1110, Preserve TestSetup suite fixtures as static Jupiter lifecycle methods. #1146.ExpectedExceptionmatcher type:ExpectedExceptionToAssertThrowsemits uncompilable code whenexpect(Matcher)is given a matcher narrower thanThrowable#1124, ExpectedExceptionToAssertThrows: declare captured exception with the matcher's exception type #1129.MockitoWhenOnStaticToMockStatic:JUnit4to5Migrationcreates onemockStaticregistration per stubbing inside@Before, which Mockito rejects at runtime #1123, Reuse a single MockedStatic for consecutive stubbings of the same class in @Before #1128.MockUtilsToStatic: Only removeMockUtildeclarations whose uses are all migrated #1084, KeepMockUtildeclarations used across files and switch labels #1086.- Half-migrated classes in general: JUnit4to5Migration: leave a class it cannot finish on JUnit 4, still running, instead of half-migrating it #1167.
- Kotlin across the JUnit 4 migration: Add Kotlin support across the JUnit 4 to Jupiter migration #1189.
- Parameterized setup:
A. Wiring & placement (decide first)
-
A1 Drop
Mockito1to4MigrationfromMockito4to5OnlyDetails
- Where:
Y/mockito.yml:50-59. - Problem: ef62c31 created
Mockito4to5Onlyso the Spring Boot 3.1 upgrade could include Mockito 5 without re-running the 1→4 migration. The commit moved the oldMockito1to5MigrationrecipeList across wholesale, including its first entryMockito1to4Migration, so the split never took effect. The description ("Does not include 1.x to 4.x migration") has been wrong ever since, andrecipes.csvcounts 101 sub-recipes against 102 for the full migration. - Impact: rewrite-spring's Boot 3.x upgrades reach
ReplacePowerMockitothis way. That comments out test bodies and removes PowerMock (see Issue when MockedStatic field is defined with an inline Mockito.mockStatic initialiser #1127), which is probably not what a Boot upgrade should do. - Fix: remove the entry;
Mockito1to5Migrationalready chains both halves. Then decide whether Boot upgrades should run the PowerMock replacement at all.
- Where:
-
A2 Stop adding
ExternalResourceSupportwithout its dependency, then deleting the dependencyDetails
- Where:
- The usage is added by
M/junit5/HandleExternalResourceRules.java:74-83, wired atY/junit5.yml:163. - The dependency is removed by
RemoveJupiterMigrationSupportinY/junit-jupiter.yml:50,53-68(insideJupiterBestPractices), and by a copy inY/junit6.yml:62-64.
- The usage is added by
- Problem:
- The JUnit 4→5 migration emits
@ExtendWith(ExternalResourceSupport.class), but nothing addsjunit-jupiter-migrationsupport. JUnit5BestPractices,JUnit6BestPracticesandJUnit5to6Migrationthen remove that artifact with no usage guard. migrationsupport 6.1.3 still shipsExternalResourceSupportand@EnableRuleMigrationSupport, so any project that uses them breaks.
- The JUnit 4→5 migration emits
- Fix:
- Add the dependency with
onlyIfUsing: org.junit.jupiter.migrationsupport..*. - Guard both removals with
unlessUsingon the same pattern. - Chain
RemoveJupiterMigrationSupportinjunit6.ymlinstead of copying it.
- Add the dependency with
- Also (Suspected): migrationsupport depends on
junit:junit, whichExcludeJUnit4UnlessUsingTestcontainers(Y/junit5.yml:166-181) would exclude.
- Where:
-
A3 Wire, delete or mark opt-in the unreachable recipes
Details
RemoveJreOther(M/junit6/RemoveJreOther.java) was added in Add JUnit 6.1 migration recipes #1002 next to the other 6.1 steps but never wired. It also has bugs; see C13. Fix those before wiring it intoJUnit5to6Migration.RemoveEmptyTests(M/cleanup/RemoveEmptyTests.java) was never wired. It deletes tests, including an empty@Overridethat deliberately disables an inherited test (:75-78, Suspected). Decide whether it belongs incleanup.BestPractices.MockitoJUnitRunnerSilentToExtensionis@Deprecatedand was superseded in Utilize MockitoJUnitRunnerToExtension for UseMockitoExtension #711. Delete it.AddMissingNestedwas unwired in The JUnit 4 migration adds@Nestedonly inside@RunWith(Enclosed.class)suites #1160 and is now only used throughEnclosedToNested. Add it toJupiterBestPractices(RSPEC-S5790), or document that it is standalone.AddMockitoJupiterDependencyonly appears as a comment (Y/mockito.yml:195-201, MockitoExtension gets added even when not necessary #875).AnyStringToNullableis reachable only throughdoAfterVisit(M/mockito/AnyToNullable.java:65); see F11.
-
A4 Decide how much the JUnit 4→5 migration and
Assertjshould pull inDetails
- What
JUnit4to5Migrationpulls in:- Mockito 4 and PowerMock replacement (via
UseMockitoExtension, thenMockito1to4Migration, thenReplacePowerMockito). The PowerMock replacement disables tests. - OkHttp
mockwebserver35.x (Y/junit5.yml:118). MigrateHamcrestToJUnit5throughJupiterBestPractices(Y/junit-jupiter.yml:29). That recipe has the worst bugs in this issue (E1, E2).
- Mockito 4 and PowerMock replacement (via
- What
Assertjpulls in: about 1048 recipes, including all ofJUnit5BestPracticesthroughJUnitToAssertj. - Decision: keep these as they are, or make the cross-library steps opt-in. At the very least, fix E1 before more users reach it.
- What
-
A5 Testcontainers 2: decide the Kafka target
Details
- Where:
Y/testcontainers.yml:653-655(Testcontainers2ContainerClasses),:52-54(MigrateToKafkaNative),:287-290(ExplicitContainerImages). - Problem:
- Every
org.testcontainers.containers.KafkaContainerbecomesorg.testcontainers.kafka.KafkaContainer. Bytecode shows the new class assertsapache/kafkaimages and has no no-arg constructor,withEmbeddedZookeeperorwithKraft. Confluent users fail at runtime or at compile time. ExplicitContainerImagesinjectsconfluentinc/cp-kafka:5.4.3, soTestcontainers2Migrationrun on its own guarantees that failure.- The old class still exists in Testcontainers 2.0.5, so the
ChangeTypeisn't needed to compile.
- Every
- Options: map Confluent usage to
ConfluentKafkaContainer, or retarget only after the image has actually been switched (insideMigrateToKafkaNative). - Also (Suspected): in
TestContainersBestPractices(:63-65), theChangeTypes inMigrateToKafkaNativeandMigrateToOracleFreerun beforeExplicitContainerImages. That recipe's<constructor>()matcher targets the old FQN, sonew KafkaContainer()ends up as a constructor that doesn't exist. Probe:new KafkaContainer()underTestContainersBestPractices.
- Where:
-
A6
AddMockitoExtensionIfAnnotationsUsed: disabled inmockito.yml, still active injmockit.ymlDetails
- Where:
Y/mockito.yml:195-201(disabled with the MockitoExtension gets added even when not necessary #875 rationale) versusY/jmockit.yml:50. - Also:
M/mockito/AddMockitoExtensionIfAnnotationsUsed.java:59-61,73-74adds@ExtendWith(MockitoExtension::class)to Kotlin JUnit 4 tests. This is Confirmed.
- Where:
-
A7 Remove duplicate and copied entries
Details
RemoveDoNothingForDefaultMocksis atY/mockito.yml:32and is reached again via:206.- The
junit-jupiter-paramsscanners inY/junit5.yml:139-145duplicateM/junit5/AddJupiterDependencies.java:58-62. Y/junit6.yml:62-64copiesRemoveJupiterMigrationSupport(see A2).M/mockito/PowerMockitoMockStaticToMockito.java:92-93queues the sameChangeMethodTargetToStaticthat runs next inY/powermockito.yml:188-191.
-
A8 Decide on AssertJ's
actual()floor (3.27)Details
- Problem:
AbstractAssert#actual()is@since 3.27.0.ReturnActualand the value path ofJUnitAssertThrowsToAssertExceptionType(:106-108,117-119) emit it. - The
AddDependency 3.xwithacceptTransitive: trueinJUnitToAssertj(Y/assertj.yml:549-554) doesn't raise an existing older AssertJ, such as Spring Boot ≤3.4's 3.26.x. - Options: bump AssertJ to 3.27.x in
Assertj, or bail out. See D3 for the separate compile error on collections.
- Problem:
B. JUnit 4 → 5 migration bugs
-
B1(Only turn theUseWiremockExtensionturns every@Rulein the file into@RegisterExtension(PROBED)WireMockRulefield's@Ruleinto@RegisterExtensioninUseWiremockExtension#1218)Details
- Where:
M/junit5/UseWiremockExtension.java:44-49,68-69. - Problem: a file-wide
ChangeType(org.junit.Rule → RegisterExtension)is queued whenever anew WireMockRule(..)exists. InJUnit4to5Migrationit runs atY/junit5.yml:61, beforeTemporaryFolderToTempDir(:105) andTestRuleToTestInfo(:107). Those no longer recognise their fields. - Probe:
This gives
@Rule public WireMockRule wm = new WireMockRule(); @Rule public TemporaryFolder tmp = new TemporaryFolder();
@RegisterExtension public TemporaryFolder tmp, which Jupiter rejects at runtime. - Fix: change only the annotation on the
WireMockRuledeclaration. Coordinate with Correct shared JUnit annotations and rule type matching #1191, which adds the same file-wide pattern for@ClassRule.
- Where:
-
B2
MigrateJUnitTestCase:@Teston any method namedtest*(related:MigrateJUnitTestCaseremoves theTestCase(String)constructor without updating its callers, breaking compilation #1107, A retainedTestCase(String)constructor leaves the migrated test class unrunnable under JUnit 5 #1108)Details
- Where:
M/junit5/MigrateJUnitTestCase.java:184-185. - Problem (PROBED):
private void testSum(int a, int b, int expected)gets@Test, and Jupiter then fails withParameterResolutionException. - Fix: apply JUnit 3's own discovery rule: public, non-static, void, no parameters.
- Also (Suspected,
:84): a file-wideChangeType(TestCase → org.junit.Assert)retypes non-assertion uses. Probe:static void run(junit.framework.TestCase t) { t.run(); }.
- Where:
-
B3
TemporaryFolderToTempDir: two harmful mappingsDetails
newFolder()with no arguments (PROBED,:279-284with:338-345): it becomesnewFolder(root, "junit"), so a second call in the same test failsmkdirs()withIOException. JUnit 4 created a unique folder on each call. Fix:Files.createTempDirectory(root.toPath(), "junit").toFile().- Builder initializers (Confirmed,
:104-135):TemporaryFolder.builder().assureDeletion().build()is kept as the initializer of an@TempDir File. Fix: drop the initializer. - Types (
:271): the class type is put on a method-name identifier.
-
B4
TimeoutRuleToClassAnnotationremovesTimeoutfields that are still referenced (PROBED)Details
- Where:
M/junit5/TimeoutRuleToClassAnnotation.java:58-75. - Problem: any
Timeoutfield is removed, with no@Rulecheck and no reference check. - Probe:
private final Timeout t = Timeout.seconds(5); @Rule public RuleChain c = RuleChain.outerRule(t);leaves a danglingt.T/junit5/TimeoutRuleToClassAnnotationTest.java:52even uses a field without@Ruleas its input.
- Where:
-
B5
TestRuleToTestInfo: file-wide rename, lost parameters, forced spacingDetails
- Confirmed (
:71-72): an unconditional file-wideChangeType(org.junit.Before → BeforeEach). Run on its own, it leaves a JUnit 4 class with a@BeforeEachthat never runs. - Suspected (
:174-182):replaceParameters()drops parameters that an existing@BeforeEachalready has. - Confirmed (
:66): the prefix is forced to" ", solog(name.getMethodName())becomeslog( name). Usemi.getPrefix().
- Confirmed (
-
B6 System Rules recipes don't see subclasses in other files
Details
- Where:
M/junit5/SystemRules.java:46-58(mayBeUsedElsewhere),M/junit5/SystemRulesToSystemStubs.java:141-147,M/junit5/SystemPropertyRulesToPioneer.java:83-84,133-136. - Problem (Confirmed): only abstract owners or
mainsources count as "may be used elsewhere". A concreteBaseTestwhoseoutrule is read bySubTestin another file is retyped (System Stubs) or removed (Pioneer), andSubTeststops compiling. - Decision: scan across files for
owner#name, or add the existing TODO comment.
- Where:
-
B7(Remove staleAssumeNotNullToAssumptions: the varargs form produces a method that doesn't exist@Disabledtests,//noinspectionlines and restating comments #1233)Details
- Where:
M/junit5/AssumeNotNullToAssumptions.java:52-63, then theChangeType Assume → AssumptionsatY/junit5.yml:223-226. - Problem (Confirmed):
Assume.assumeNotNull(a, b)doesn't match, so it ends up asAssumptions.assumeNotNull(a, b). - Test:
T/junit5/AssumeNotNullToAssumptionsTest.java:65is@Disabledagainst Invalid rewrite ofAssume.assumeNotNulltoAssumptions.assumeNotNull#886, which is closed. Its expected output is also wrong: the input passesnew Object(), new Object(), but the expected output usesobject1, object2. - Fix: a Java visitor for the varargs form, or a TODO plus bail-out. Fix and enable the test.
- Where:
-
B8
ExpectedExceptionToAssertThrows: name clashes and lostthrows(related:ExpectedExceptionToAssertThrowsemits uncompilable code whenexpect(Matcher)is given a matcher narrower thanThrowable#1124)Details
- Suspected (
:203,229): the variable nameexceptionis hard-coded. Probe:RuntimeException exception = …; thrown.expectMessage("boom"); throw exception;. - Suspected (
:133-179): the checked-exception scan ignores the implicitclose()of try-with-resources and calls with a null method type, sothrows IOExceptionis dropped wrongly. - Suspected:
expect*inside@BeforegivesassertThrows(X.class, () -> {})in setUp. - This is separate from
ExpectedExceptionToAssertThrowsemits uncompilable code whenexpect(Matcher)is given a matcher narrower thanThrowable#1124 and ExpectedExceptionToAssertThrows: declare captured exception with the matcher's exception type #1129.
- Suspected (
-
B9
JUnitParamsRunnerToParameterized: static init methods that read instance stateDetails
- Confirmed (
:342-386):MakeMethodStaticonly follows method invocations, so an init method that reads an instance field no longer compiles. Probe:private final Foo foo = new Foo(); private Object[] parametersForTest() { return new Object[]{ foo.a() }; }. - Suspected (
:114,328-333):method = "first, second"is split without trimming. - Suspected (
:118): the|column separator is passed verbatim to@CsvSource.
- Confirmed (
-
B10
UpdateMockWebServer*recipes: non-compiling output and wrong mappingsDetails
UpdateMockWebServerMockResponse(Confirmed)::45-51appends.build()to everyMockResponseargument in any call, so helper parameters retyped toBuilderbreak. Fix: only wrap arguments ofMockWebServer#enqueueandQueueDispatcher#enqueueResponse.:88mapssetHttp2ErrorCode(int)tocode(int), which sets the HTTP status. The test atT/junit5/UpdateMockWebServerMockResponseTest.java:244,270locks this in.:85,91:setErrorCodeandsetThrottleBodydon't exist in 3.14.9 or 4.10.0, so these entries are dead.SocketPolicyandsetSocketPolicyare unmapped, andChangePackagesends them to a class that doesn't exist.
UpdateMockWebServer(Confirmed,:73,157): with two@Rule MockWebServerfields, only the last getsclose().:178uses4.X(uppercase).UpdateMockWebServerDispatcher(Suspected,:91-107):returnstatements inside lambdas indispatch()get wrapped.
-
B11
AddHamcrestJUnitDependency: repo-wide flag and over-broad matcherDetails
- Where:
M/junit5/AddHamcrestJUnitDependency.java:39-85. - Problem (Confirmed):
- One
assertThatanywhere addshamcrest-junitto every module. Assert.assertThatmoves toMatcherAssert, which is in plain Hamcrest; onlyAssume.assumeThatneedshamcrest-junit.
- One
- Fix:
onlyIfUsing: org.hamcrest.junit..*after the migration, and drop the scanner.
- Where:
-
B12 Smaller JUnit 4 migration bugs
Details
ParameterizedRunnerToParameterized:118-124:ClassCastExceptionon@Parameter(INDEX)with a constant. Confirmed. Related: ParameterizedRunnerToParameterized update to account for test class inheritance #91,JUnit4to5MigrationmovesParameterizedparameter assignment after@BeforeEach, so setup methods read null #1125,ParameterizedRunnerToParameterizedsilently skips classes whose@Parametersmethod is inherited, leaving a partially migrated class that #1126, JUnit 4 to 5: use @ParameterizedClass when converting parameterized tests #743.CategoryToTag:93-94:@Category({})throws aClassCastExceptiononJ.Empty. Suspected.AddMissingTestBeforeAfterAnnotations:76-84: an override inherits the super's@Test(expected/timeout)in JUnit 4, but gets a bare@Test. Suspected.UpdateTestAnnotation:162: emitsthreadMode, which needs Jupiter 5.9, while a Spring Boot 2.5 parent manages 5.7. Suspected.MockitoJUnitToMockitoExtension:82: checksorg.mockito.runners.MockitoJUnitRunner, whichY/mockito.yml:187-189has already renamed, so the guard never fires. Confirmed.EnvironmentVariables:118-128,150-158:env.clear(names)becomesenv.remove(names), which doesn't compile. Suspected.JUnitSoftAssertionsToSoftAssertionsExtension:103-106: a file-wideChangeTypebreaks helpers in other files that takeJUnitSoftAssertions. Suspected.RunnerToExtension:101-120: handles@RunWithon methods, which can't happen. Dead code.
C. JUnit 5/6 best practices and cleanup bugs
-
C1(Only drop a literal zero delta inAssertEqualsIntegralDeltaToAssertEqualsdrops non-zero deltas (PROBED)AssertEqualsIntegralDeltaToAssertEquals#1212)Details
- Where:
M/cleanup/AssertEqualsIntegralDeltaToAssertEquals.java:59,83, reached fromJupiterBestPractices(Y/junit-jupiter.yml:34). - Problem:
assertEquals(100, elapsed, 50)becomesassertEquals(100, elapsed), so a tolerance test starts failing. The description (:43-45) wrongly says the delta is meaningless, and every test uses0. - Fix: only act when the delta is a literal zero.
- Where:
-
C2
CleanupKotlinJUnit5AssertionImportsremovesAssertions.*(PROBED)Details
- Where:
M/junit5/CleanupKotlinJUnit5AssertionImports.java:53,67-90, reached fromJupiterBestPractices(Y/junit-jupiter.yml:31). - Problem: with
import org.junit.jupiter.api.*, everyAssertionsimport is dropped. The KotlinAssertionsKtfunctions only coverassertAll,assertDoesNotThrow,assertInstanceOf,assertNotNull,assertNull,assertThrows,assertTimeout*andfail, soassertEquals(1, 1)no longer resolves. The tests only useassertNullandassertNotNull. - Fix: only drop explicit imports of names that have a Kotlin counterpart. Leave
Assertions.*alone, and add a precondition.
- Where:
-
C3(Leave tests that call assumptions alone inTestsShouldIncludeAssertionsturns skipped tests into failures (PROBED)TestsShouldIncludeAssertions#1213)Details
- Problem 1 (
M/cleanup/TestsShouldIncludeAssertions.java:44-61,126-131): assumptions don't count as assertions.assumeTrue(isLinux()); service.start();is wrapped inassertDoesNotThrow, which reports theTestAbortedExceptionas a failure. - Problem 2 (
:172-198, Confirmed): the check reduces over the whole class, so one asserting helper called anywhere exempts every test. - Also:
validate()(:77-88) can never fail.
- Problem 1 (
-
C4(Keep early returns inTestMethodsShouldBeVoidandKotlinTestMethodsShouldReturnUnitdelete early returns (PROBED)TestMethodsShouldBeVoidandKotlinTestMethodsShouldReturnUnit#1217)Details
- Early returns:
RemoveDirectReturns(M/cleanup/TestMethodsShouldBeVoid.java:93-98,M/cleanup/KotlinTestMethodsShouldReturnUnit.java:120-128) deletes everyreturn.if (skip) { return 0; } fail();then always fails.- Fix: turn
return expr;intoexpr;followed byreturn;. - Fix: bail out when the method is invoked anywhere, or is an override.
- Fix: turn
- Kotlin expression bodies (Confirmed):
KotlinTestMethodsShouldReturnUnit:82-91adds: Unitto expression bodies of any type, sofun t() = assertThat(x).isTrue()no longer compiles. The test inputs are not valid Kotlin. - Suspected:
TestMethodsShouldBeVoidhas no Kotlin guard and installs aJ.Primitivevoid, which the Kotlin printer may print asvoid. - Suspected:
:76'srequireNonNull(m.getBody())NPEs on abstract test methods.
- Early returns:
-
C5
RemoveTestPrefixbreaks overrides and implied@MethodSource(PROBED)Details
- Overrides:
ChangeMethodName(.., matchOverrides=false)(M/cleanup/RemoveTestPrefix.java:180) renamesBTest.testFoobut not the@Overridein a subclass, so the override no longer compiles. - Confirmed (
:150vs:160,175): the source-method rename is scheduled before the bail-outs. After a bail-out, the factory is calledfoo()while the test is still calledtestFoo.
- Overrides:
-
C6
TestsShouldNotBePublicnarrows methods of extended classes (related:TestsShouldNotBePublicshould reduce visibility if used elsewhere in Javadoc links #458)Details
- Where:
M/cleanup/TestsShouldNotBePublic.java:102vs:150-163. - Problem (Confirmed): the
extendedClassesguard only protects the class modifier. A public base's@BeforeEach public void setUp()becomes package-private, which breaks subclasses in other packages. - This is different from
TestsShouldNotBePublicshould reduce visibility if used elsewhere in Javadoc links #458.
- Where:
-
C7 Cleanup assertion rewrites without type checks (probed part: Only replace
assertNotEquals(false, <boolean>)when the second argument is a primitiveboolean#1220)Details
PROBED:(Only replaceAssertNotEqualsBooleanToAssertBoolean:47doesn't check that the second argument is boolean (its sibling does,AssertEqualsBooleanToAssertBoolean:48).assertNotEquals(false, map.get("k"))becomesassertTrue(Object).assertNotEquals(false, <boolean>)when the second argument is a primitiveboolean#1220)- Confirmed:
AssertionsArgumentOrder:53-54,128-130matches JUnit 4(String, Object, Object)overloads by index, so it swaps the message.assertEquals(..)is listed twice (:50-51), while JUnit 4assertNotEqualsis missing. - Confirmed (rare):
SimplifyTestThrows:95-97turnsthrows IOException, Throwableintothrows Exception. - Suspected:
AssertTrueComparisonToAssertEquals:112-113turnsInteger == longinto an ambiguousassertEquals. - Suspected:
AssertTrueEquals:93andAssertFalseEquals:90matchequalsby name, and pass a null select for an implicit receiver. - Suspected: seven templates hard-code
Assertions.for any non-null select:AssertEqualsBoolean:57,AssertNotEqualsBoolean:56,AssertFalseEquals:54,AssertFalseNegation:52,AssertFalseNull:56,AssertTrueComparison:55,AssertEqualsIntegralDelta:63,87. Renaming in place, asAssertTrueNegationToAssertFalsedoes, fixes this.
-
C8
CsvSourceToValueSourcechanges values on the non-text-block path (PROBED)Details
- Where:
M/junit5/CsvSourceToValueSource.java:111-135. It skips the checks the text-block path makes at:100-110. - Probe:
@CsvSource({"x, y", "z, w"}) void t(String s)becomes@ValueSource(strings = {"x, y", "z, w"}), sosis"x, y"instead of"x". - Other values that change: quotes (
"'a'"), surrounding whitespace, and""(which CSV reads as null). - Suspected: Kotlin
@ValueSource(strings = "apple")needs[...].
- Where:
-
C9
ImplausibleTimeoutToMinutesdropsthreadMode(PROBED) and roundsDetails
- Where:
M/junit5/ImplausibleTimeoutToMinutes.java:71-96. - Problem:
@Timeout(value = 3600, threadMode = SEPARATE_THREAD)becomes@Timeout(value = 60, unit = MINUTES), losingthreadMode. - Also:
:89rounds (1000 s becomes 17 min), although the description says "equivalent".
- Where:
-
C10
MinimumJreConditionsrewrites every JRE range in the file (PROBED)Details
- Where:
M/junit6/MinimumJreConditions.java:180-181,217-231. - Problem: collapsing one method's range queues a file-wide
ChangeTypeand attribute edits. WithjavaVersion17,@EnabledForJreRange(min = JAVA_21)on another method becomes@EnabledOnJre(JAVA_17). - Suspected (
:195-200): JRE-constant arrays write the intversionsattribute. - Suspected (
:321-325):Integer.parseInton a constant reference throws. - No precondition (
:72-73).
- Where:
-
C11(Only migrate the two-argumentRemoveInterceptDynamicTestdeletes an overload that JUnit 6 still has (PROBED)InvocationInterceptor.interceptDynamicTestinRemoveInterceptDynamicTest#1221)Details
- Where:
M/junit6/RemoveInterceptDynamicTest.java:31-32,47-48. - Problem:
interceptDynamicTest(..)also matches the 3-argument(Invocation, DynamicTestInvocationContext, ExtensionContext), which is still in 6.1.3, so user interceptors are deleted. - Also: deleting the 2-argument override discards its logic.
- Fix: match the 2-argument signature exactly, and migrate it to the 3-argument form by adding the parameter.
- Where:
-
C12(Only convert Java sources inUseAssertSameon Kotlin turns equality into identity (PROBED)UseAssertSame#1223)Details
- Where:
M/junit5/UseAssertSame.java:59-76. - Problem: Kotlin
assertTrue(listOf(1) == listOf(1))becomesassertSame(listOf(1), listOf(1)), because==callsequalsin Kotlin. - Fix: skip
K.CompilationUnit. - Types (
:94-106): the fallback "stubbed" method type is the normal path.
- Where:
-
C13
RemoveJreOtherempties lone braced arrays and changes behaviourDetails
- Where:
M/junit6/RemoveJreOther.java:78-84. - Problem (Confirmed):
@EnabledOnJre({JRE.OTHER})becomes@EnabledOnJre({}), which JUnit rejects. That contradicts the recipe's own description (:50-53).- Dropping
OTHERfrom{JAVA_21, OTHER}disables the test on future JREs.
- The recipe is currently unwired (A3).
- Where:
-
C14
AssertThrowsOnLastStatement: side effects outside the targetDetails
- Confirmed (
:72):doAfterVisit(new LambdaBlockToExpression())is scheduled for every method body, so it rewrites unrelated lambdas. - Confirmed (
:123): an empty block lambda() -> {}deletes the wholeassertThrowsstatement. - Suspected (
:123-125,158,290): hoisted locals from siblingassertThrowscalls can get duplicate names. - Not reproduced: hoisting a throwing argument out of a single-statement lambda. A
System.out.println(Integer.parseInt("x"))probe stayed inside the lambda. - Test: a bogus
@Issue(".../issues/2861")atT/junit5/AssertThrowsOnLastStatementTest.java:1287.
- Confirmed (
-
C15 Smaller JUnit 5 best-practice items
Details
AddMissingNested:114mutates the modifier list in place withremoveIf. UseListUtils.filter.AddParameterizedTestAnnotation:36-46misses@FieldSource,@ArgumentsSourcesand custom sources. UseAnnotationMatcher("@…ArgumentsSource", true). Coordinate with Respect composed JUnit test templates #1093.AssertTrueInstanceofToAssertInstanceOf:77-96: its JUnit 4 branch emits Jupiter'sassertInstanceOfinto JUnit 4 modules whenJupiterBestPracticesruns on its own. Needs a decision; coordinate with Keep an explicit JUnit owner when migrating a qualifiedassertTrue(x instanceof Y)#1083 and Preserve assertion owners and receiver evaluation inassertInstanceOfmigration #1088.
D. AssertJ bugs
-
D1(Only adoptAdoptAssertJDurationAssertions: comparisons become equality, and units are lossy (PROBED)hasSecondsforisEqualToinAdoptAssertJDurationAssertions#1215)Details
- Comparisons (PROBED,
M/assertj/AdoptAssertJDurationAssertions.java:105-106,142-150):assertThat(d.getSeconds()).isGreaterThan(5)becomesassertThat(d).hasSeconds(5). getNano(Confirmed by running it,:67-68):getNano()is the nanosecond-of-second part, buthasNanoschecks the whole duration.- Units (Confirmed by running it,
:163-200):hasMillis(5000)becomeshasSeconds(5), which also passes for 5999 ms.- A zero argument recurses all the way to
hasDays(0). Math.toIntExactthrows for large values.
- Tests lock in the bugs:
getNanoToHasNanos,isEqualToVariableand thesimplifyDurationAssertionsCSV. - Fix: only map from
isEqualTo, drop thegetNanomapping, and remove the unit up-conversion.
- Comparisons (PROBED,
-
D2 JUnit-to-AssertJ recipes mishandle deltas (PROBED)(Keep JUnit deltas when convertingassertEquals,assertNotEqualsandassertArrayEqualsto AssertJ #1216)Details
- PROBED (
JUnitAssertEqualsToAssertThat:88-94,105-112):assertEquals(100, elapsed, 10)becomesassertThat(elapsed).isEqualTo(100). - Confirmed with javac (
JUnitAssertNotEqualsToAssertThat:75-90,106-115,JUnitAssertArrayEqualsToAssertThat:70-86,105-114): these decide "delta or message" from the argument's own type. The Fix 6 issues in AssertJ recipe migrations #911 fix was only applied toassertEquals.assertNotEquals(0.0, d, 0)becomes.as(0), andassertNotEquals(5, count, 1.0)calls a method that doesn't exist. - Fix: share
floatingPointDeltaType/isIntegralType. Keep the delta viaisCloseTo(.., within(..)), or bail out unless it is a literal0.
- PROBED (
-
D3
ReturnActualemits code that doesn't compile for collectionsDetails
- Where:
M/assertj/ReturnActual.java:81. - Problem (Confirmed with javac): for
List,IterableandCollection,actual()returnsList<? extends E>, so the@DocumentExampleoutput (return assertThat(list).isNotNull().actual();in a method returningList<String>) doesn't compile. - Fix: bail out when the
assertThatoverload'sACTUALis a wildcard. Version floor: A8.
- Where:
-
D4
SimplifyHasSizeAssertionproduces ahasSameSizeAsthat fails at runtime (PROBED)Details
- Where:
M/assertj/SimplifyHasSizeAssertion.java:59-70. - Problem:
assertThat(list).hasSize(str.length())becomeshasSameSizeAs(str), which binds tohasSameSizeAs(Object array)and fails. The same happens for a list against a map, and for an array against a string. - Fix: only rewrite when the asserted type and the size source are compatible.
- Where:
-
D5
SimplifyRedundantAssertJChainsremoves guards that aren't redundant (PROBED)Details
- Where:
M/assertj/SimplifyRedundantAssertJChains.java:56,88,114-127. isNotEmpty()beforecontainsAll(..):assertThat(ids).isNotEmpty().containsAll(expected)loses theisNotEmpty(), althoughcontainsAll(emptyList)passes on an empty actual.isNotNull()beforematches(..):..* matches(..)also matchesAbstractAssert.matches(Predicate), which does not null-check.- Fix: type the matcher (
AbstractCharSequenceAssert matches(..)), and limit theisNotEmptyremoval.
- Where:
-
D6(Only convertJUnitAssertInstanceOfToAssertThatbreaks code that uses the return value (PROBED)assertInstanceOfused as a statement inJUnitAssertInstanceOfToAssertThat#1222)Details
- Where:
M/assertj/JUnitAssertInstanceOfToAssertThat.java:54-69. - Problem:
Integer i = assertInstanceOf(Integer.class, o);becomesInteger i = assertThat(o).isInstanceOf(Integer.class);. - Fix: bail out unless the call is a statement, as
JUnitAssertThrows#hasReturnTypedoes.
- Where:
-
D7
JUnitAssertThrowsToAssertExceptionTyperetypes signaturesDetails
- Where:
M/assertj/JUnitAssertThrowsToAssertExceptionType.java:82-92,164-192,209-224. - Confirmed:
RetypeExecutableVariableretypes method parameters and non-private fields, and only checks the current file. A helperassertFails(Executable e)therefore becomesThrowingCallable, and its callers in other files break. - Confirmed:
assertThrows(X.class, this.executable)orexecutable()emitsisThrownBy(<Executable>), which doesn't compile. - Fix: only retype locals, and bail out on any other
Executableexpression.
- Where:
-
D8 Other AssertJ semantics changes
Details
SimplifyAssertJInstanceOfAssertion:93(Confirmed by running it):assertThat(x instanceof Foo).isFalse()passes for null, butisNotInstanceOffails on null.JUnitFailToAssertJFail(Confirmed)::63-74:fail(Supplier)becomesfail("", supplier), so the message is silently"".:84-85: a whole-fileRemoveUnusedImports.:89-110: unqualifies everyAssertions.failin the file.
JUnitTryFailToAssertThatThrownBy:- Confirmed (
:145,152): messages are inserted raw, so a quote or backslash breaks the template. UsegetValueSource(). - Confirmed (
:141,148):getMessage()is matched on any receiver. - Suspected (
:167-169): a single-statement body that is a declaration or an outer assignment. - No precondition (M1).
- Confirmed (
MigrateAssertionsForClassTypes:- Confirmed (
:47-63,92-95): run on its own, it emitsAssertionsForClassTypes.assertThatObject, which doesn't exist. - Suspected:
COLLISION_TYPESmissesAtomic*,LongAdder,BigInteger,CharSequence,CompletionStage,FutureandSpliterator.
- Confirmed (
SimplifyChainedAssertJAssertion:222-227(Confirmed): generatesPath.of(..), which is Java 11+. UsePaths.get.IsEqualToIgnoringMillisToIsCloseTo(Confirmed by running it): weaker semantics than the original, not mentioned in the description. Its display name says "byby isCloseTo".- Refaster (
AssertJDoubleRules:114-116,137-139,160-162,AssertJFloatRules:96-97,117-118,138-139):isSameAs(0)can never pass, so rewriting it toisZero()changes behaviour. Low impact. - Suspected: an unqualified
size(),stream()orgetFirst()inside a collection subclass passes a null select (SimplifyHasSizeAssertion:64,SimplifyStreamMapToExtracting:79,SimplifySequencedCollectionAssertions:151). - Suspected: Kotlin
.as(..)from theJUnit*ToAssertThattemplates;asis a Kotlin keyword. There are no Kotlin tests inT/assertj/.
-
D9 Dead YAML entries and null-able options in
Simplify*AssertJAssertionDetails
- Entries that can never match (Confirmed):
Y/assertj.yml:164-168(String.getString),:234-238(assertThat(int).hasSize),:239-243(File.getFile). - Options:
SimplifyAssertJAssertion.assertToReplace(:39-44) and all fourSimplifyChainedAssertJAssertionoptions (:47-73) arerequired = false, but they are spliced straight into matcher strings. Make them required.
- Entries that can never match (Confirmed):
E. Hamcrest & Truth bugs
-
E1(Track HamcrestRemoveNotMatcherVisitor: a negation leaks into later assertions and flips them (PROBED)not(..)negation locally instead of on theExecutionContext#1214)Details
- Where:
M/hamcrest/RemoveNotMatcherVisitor.java:31-40,47-50,67,71-72, used byHamcrestMatcherToJUnit5.java:155-177andHamcrestInstanceOfToJUnit5.java:73-82. - Problem: whether an assertion is negated is stored on the
ExecutionContext, keyed by the printed matcher. The probe shows it flips assertions even within one file:becomesassertThat(a, not(nullValue())); assertThat(b, nullValue());
assertNotNull(a); assertNotNull(b);. - Also:
not(not(x))NPEs (:47-48). - Reach:
JupiterBestPractices→JUnit5BestPractices,JUnit6BestPractices,JUnitToAssertj. - Why the tests miss it:
RewriteTest's immutable-context check catches this, but eight tests turn it off withimmutableExecutionContext(false):HamcrestMatcherToJUnit5Test:131,170,209,552,1066HamcrestInstanceOfToJUnit5Test:38RemoveNotMatcherTest:36MigrateHamcrestToJUnitTest:42
- Fix: count the
not(..)layers locally, drop the context messages, and remove the relaxations.
- Where:
-
E2
HamcrestMatcherToJUnit5picks templates by simple name, ignoring typesDetails
- Where:
M/hamcrest/HamcrestMatcherToJUnit5.java:60,65,70-75,172(Replacement.valueOf(getSimpleName().toUpperCase())). - Problem (Confirmed):
greaterThanonString,BigDecimalorLocalDateemitsassertTrue("b" > "a").closeTo(BigDecimal, ..)emitsMath.abs(bd - bd).closeTouses<where Hamcrest uses<=.equalToon arrays becomes a reference comparison.- Arguments come out as (actual, expected).
- Fix: one typed
MethodMatcherper replacement, a numeric guard,<=, andassertArrayEqualsfor arrays.
- Where:
-
E3
HamcrestMatcherToAssertJ/HamcrestNotMatcherToAssertJpass Hamcrest matchers into AssertJ (PROBED)Details
- Where:
M/hamcrest/HamcrestMatcherToAssertJ.java:74,93,M/hamcrest/HamcrestNotMatcherToAssertJ.java:71,94,127. - Problem: the
*(org.hamcrest.Matcher)exclusion doesn't match theMatcher[]varargs overloads.contains(equalTo("a"), startsWith("b"))becomescontainsExactly(equalTo("a"), startsWith("b")), and the same happens forcontainsInAnyOrder,hasItems,arrayContainingandhasEntry(Matcher, Matcher). - Test locks in non-compiling output:
T/hamcrest/HamcrestMatcherToAssertJTest.java:386-428, hidden withmethodInvocations(false). - Fix: bail out if any argument is (or contains) a
Matcher. - Also: the 3-argument path of
HamcrestNotMatcherToAssertJ(:143-147) leaves thenotand matcher imports unused.
- Where:
-
E4 Hamcrest 1.x
is(Class)is migrated as equalityDetails
- Where:
M/hamcrest/HamcrestIsMatcherToAssertJ.java:42-43,61,M/hamcrest/HamcrestHasPropertyToAssertJ.java:45-46,103. - Problem (Confirmed with javap): in hamcrest-core 1.3,
is(Class<T>)is the deprecated alias forinstanceOf, soassertThat(s, is(String.class))becomesisEqualTo(String.class). - Fix: exclude
is(java.lang.Class)or map it toisInstanceOf. The test needshamcrest-core-1.3inclasspath.tsv.gz.
- Where:
-
E5 Hamcrest YAML mappings that change behaviour
Details
equalToIgnoringWhiteSpace(Y/hamcrest.yml:230-232,329-331): it maps toisEqualToIgnoringWhitespace, which removes all whitespace in AssertJ 3.9+. The equivalent isisEqualToNormalizingWhitespace.not(hasItems(a, b))(:360-362): it becomesdoesNotContain(a, b), which is stricter.
-
E6
HamcrestInstanceOfToJUnit5: crash and null handlingDetails
- Crash (Confirmed):
:72casts the matcher toJ.MethodInvocation, soMatcher<Object> m = instanceOf(String.class); assertThat(o, m);throws aClassCastException. - Null (Confirmed):
:84turnsnot(instanceOf(X))intoX.class.isAssignableFrom(o.getClass()), which NPEs on null. - Suspected: the Java-only template runs inside Kotlin files from
MigrateHamcrestToJUnit5(Y/hamcrest.yml:64).
- Crash (Confirmed):
-
E7 Truth → AssertJ: find vs match, and
asListDetails
containsMatch(Confirmed): Truth'scontainsMatchis a find, but it maps to full-match assertions:M/truth/TruthThrowableAssertions.java:83-95useshasMessageMatching.Y/truth.yml:53-58maps it tomatches, anddoesNotContainMatchtodoesNotMatch.- The repo's own expectation at
T/truth/TruthThrowableAssertionsTest.java:127-152would fail at runtime. - Fix: use
hasMessageFindingMatch,containsPatternanddoesNotContainPattern.
asList(Confirmed,Y/truth.yml:128-130):ObjectArraySubject asList()becomescontainsExactly(), which asserts that the array is empty.- Suspected:
Y/truth.yml:224-226removes Truth even whenExpectorCorrespondenceremain. - Dead entries:
:120-122(AbstractMapAssert containsEntries) never matches.:199-204targetsAbstract*ArrayAssert, but AssertJ returnsListAssert.
F. Mockito & PowerMock bugs
-
F1(Only remove a directRemovePowerMockClassExtensionsremoves the user's own base class (PROBED)PowerMockTestCaseorPowerMockConfigurationsuperclass inRemovePowerMockClassExtensions#1224)Details
- Where:
M/mockito/RemovePowerMockClassExtensions.java:58-59. - Problem:
isAssignableTo(PowerMockTestCase, extends)also stripsclass FooTest extends MyBaseTest, whereMyBaseTest extends PowerMockTestCase.FooTestthen loses its inherited helpers. - Fix: use
TypeUtils.isOfClassType.
- Where:
-
F2
DisableUnsupportedPowerMockTestsleaves some classes on PowerMock, but the dependency is removed anywayDetails
- Where:
M/mockito/DisableUnsupportedPowerMockTests.java:98-101,254-267. - Problem (Confirmed): when
frameworkOf(cd)is null, the class is returned unchanged. That covers abstract base classes with an unsupported@Before, and TestNG classes that use a class-level@Test.RemovePowerMockDependencies(Y/powermockito.yml:45) still removes PowerMock, so one base class breaks the whole module. - Also:
@ParameterizedTest,@RepeatedTestand@Theoryaren't recognised as tests. - Also:
FlagUnsupportedPowerMockUsage.java:47-49still says such repositories "are left on PowerMock", which is no longer true.
- Where:
-
F3
CloseUnclosedStaticMockskeeps a static mock active past an explicitclose()(PROBED)Details
- Where:
M/mockito/CloseUnclosedStaticMocks.java:183-191,232-255,275-284. - Problem:
m.close(); assertEquals(42, Num.getNumber());is wrapped in try-with-resources and theclose()is removed, so the assertion now runs against the mock. - Fix: skip declarations that are already closed, or end the try at the close.
- Where:
-
F4
MockConstructionToTryWithResources: variable scope and early closesDetails
- Where:
M/mockito/MockConstructionToTryWithResources.java:84-91,133-136,163-165. - Problem (Confirmed):
- A local declared before
close()and used after it moves into the try body and goes out of scope. - A mid-body
close()is removed. - There is no precondition (
UsesMethod<>(MOCK_CONSTRUCTION_MATCHER)fits). - The template uses an untyped
try (#{any()})withcontextSensitive().
- A local declared before
- Probe:
MockedConstruction<A> mc = mockConstruction(A.class); A a = new A(); mc.close(); a.method();.
- Where:
-
F5 Whitebox → reflection recipes don't compile for generic, array and lambda shapes
Details
- Erased types (Confirmed):
WhiteboxToReflectionVisitor.java:571-575,719-727usesgetClassName().List<String> l = Whitebox.getInternalState(..)becomesList l = (List) ...String[]becomesObject.- The same code path is used by the
GetInternalState,InvokeMethodandInvokeConstructorrecipes.
- Lambda blocks (Confirmed,
:362-387vs:522-531): a statement-level call inside a lambda block emitsgetDeclaredField, which throws a checked exception inside aConsumer. - Fix: print the declared type expression or bail out, and apply the lambda guard to every path.
- Erased types (Confirmed):
-
F6
MockitoWhenOnStaticToMockStatic: nested classes (related:JUnit4to5Migrationcreates onemockStaticregistration per stubbing inside@Before, which Mockito rejects at runtime #1123)Details
- Where:
M/mockito/MockitoWhenOnStaticToMockStatic.java:174,202,223,387-394. - Invalid name (Confirmed): variable names come from an unsanitised
getClassName(), givingmockTest.Files1. - Wrong class (Confirmed):
when(Outer.Inner.value())mocksOuter. - Also: the mutable
varCounteron the Recipe (:57) isn't thread-safe and depends on run order. - Fix: use
getMethodType().getDeclaringType()and.replace(".", "_"). Coordinate with Reuse a single MockedStatic for consecutive stubbings of the same class in @Before #1128, which touches these lines.
- Where:
-
F7
RemoveInitMocksIfRunnersSpecifiedremoves initialisation the extension doesn't replace (PROBED)Details
- Where:
M/mockito/RemoveInitMocksIfRunnersSpecified.java:61-62,109-114,303-309. - Problem:
MockitoAnnotations.openMocks(this)in the constructor of a static, non-@Nestedfixture class is removed, so its@Mockfields stay null.openMocks(helper)is removed too.
- Fix: require a
thisargument, and only treat@Nestedinner classes as covered by the extension.
- Where:
-
F8
RemoveDoNothingForDefaultMocksremoves adoNothing()that overrides an earlier stubDetails
- Where:
M/mockito/RemoveDoNothingForDefaultMocks.java:154-193. - Problem (Confirmed): a
doThrow(..).when(foo).bar()in@Before, followed bydoNothing().when(foo).bar()in a test, turns the test into one that throws. - Also: a
HashSet<JavaType.Variable>(:63); see L1. - Also: a whole-file
RemoveUnusedImports(:162).
- Where:
-
F9
PowerMockitoDoStubbingToMockito: ambiguous private overloadsDetails
- Where:
M/mockito/PowerMockitoDoStubbingToMockito.java:85-87,125-136,154-155. - Problem (Confirmed): the private bail-out only fires when
resolveTargetMethodfinds a unique match. With two privatehelper(..)overloads, or missing types, it emitsdoNothing().when(s).helper("a")against a private method. - Also: the description (
:47-49) over-claims, and it uses a hand-builtTextComment(:88-92). - Test: turns off identifier validation in
defaults(T/mockito/PowerMockitoDoStubbingToMockitoTest.java:40-42).
- Where:
-
F10 Smaller Mockito items
Details
ReplaceMockitoTestExecutionListener:205(Confirmed):firstEnclosingOrThrow(J.CompilationUnit.class)throws on Kotlin. The wrapper recipes…ForJupiter,…ForJUnit4and…ForTestNGare never loaded in a test.ArgumentMatcherToLambda:180-184(Confirmed):ArgumentMatcher<T>and<String[]>are cast toArgumentMatcher<Object>.PowerMockRunnerDelegateToRunWith:77-86(Suspected): with a delegate,@Mockinitialisation is dropped. Add aMockitoJUnit.rule().PowerMockWhiteboxSetInternalStateToInjectMocks:89-120(Suspected):@InjectMocksinjects more than the one field the call set.ThenThrowCheckedExceptionToRuntimeException:110-116(Suspected, low):throws Ecounts as undeclared. AShallowClassis used at:75-77.MockitoBestPracticesremoves every@MockitoSettings(strictness = WARN), including the onesMockito1to3Migrationadds in the same run. Intended?
-
F11 Make
AnyToNullable/AnyStringToNullabledeclarativeDetails
- Where:
M/mockito/AnyToNullable.java:41-65,M/mockito/AnyStringToNullable.java:36,55. - Problem (Confirmed):
- The matchers use
org.mockito.Mockito, so calls throughimport static org.mockito.Matchers.*are missed.Y/mockito.yml:115-116already usesorg.mockito.*for this reason. - A global
AtomicBooleanconverts every module once any module usesmockito-all.
- The matchers use
- Fix: a YAML recipe with a
ModuleHasDependencyprecondition andorg.mockito.*patterns. This also replaces thedoAfterVisit. - Related:
VerifyZeroToNoMoreInteractionsuses the same scanner pattern, a misnamedASSERT_INSTANCE_OF_MATCHER(:33), and a garbled description.
- Where:
G. JMockit & EasyMock bugs
-
G1
JMockitBlockRewritersilently drops expectations and verifications (related: JMockit to Mockito - rewrite expectation results with the correct type #508)Details
- Where:
M/jmockit/JMockitBlockRewriter.java:121-147,233-236,303-306,499,502-531,M/jmockit/JMockitBlockToMockito.java:82-87. - Problem (Confirmed): the block is removed before rewriting, and a failure is never rolled back. Code is lost in four ways:
- A result type the template can't handle (array, type variable) drops that stubbing.
rewriteFailed =instead of|=lets a later success erase an earlier failure.- Invocations whose select isn't an identifier (cascades,
Outer.this.foo.x(), statics) are discarded. @Capturingfields aren't recognised as mocks (SetupStatementsRewriter.java:195-201).
- Probes:
new Expectations(){{ foo.names(); result = new String[]{"a"}; foo.count(); result = 1; }};new Verifications(){{ foo.getBar().run(); times = 1; }};
- Fix: rewrite into a copy, and keep the original block when any part fails.
- Where:
-
G2 JMockit
with*argument matchers are not migratedDetails
- Where:
M/jmockit/ArgumentMatchersRewriter.java:110,118,140-144,270-279. - Problem (Confirmed):
withNotNull(),withCapture(list),withInstanceOf(..)andwithSameInstance(..)are emitted unchanged insidewhen(..)andverify(..), where they no longer resolve. When mixed withanyStringthey becomeeq(withNotNull()). - Also: matchers are checked by simple name, so a local called
anycounts as one. - Fix: bail out, or map them (
notNull(),isA(),same(), …).
- Where:
-
G3
JMockitMockUpToMockito: several shapes break or are silently lostDetails
- Where:
M/jmockit/JMockitMockUpToMockito.java. All Confirmed unless marked. - JUnit 5 setup: a MockUp in
@BeforeEachis closed when setup returns, because onlyorg.junit.Before/Afterare matched (:482-494). The comment at:71claims otherwise. - Unwanted tearDown: an empty
@After tearDown()is added to any JUnit 4 class with an@Before(:89-91,140-145). - Lost code:
- A MockUp is removed with nothing in its place when no
@Mockmethod maps (:268-270,506-517). - MockUp fields and helper methods are inlined into the method body (
:187-199,281).
- A MockUp is removed with nothing in its place when no
- Unreachable code:
return null;is appended after a trailingthrow(:370-378). - NPEs:
:335on type-variable parameters. It is reached for any generic static method on the class.:361on array parameters.
- Side effects:
- A whole-file
LambdaBlockToExpression(:305). - A forced
import static org.mockito.Mockito.*even when nothing was migrated (:303). - A duplicate static import (
:286).
- A whole-file
- Where:
-
G4 Smaller JMockit items
Details
JMockitAnnotatedArgumentToMockito:82-95(Confirmed):@Mocked List<String> lbecomesmock(List<String>.class). Use the erasure.SetupStatementsRewriter:59,74,153(Confirmed code path):ClassCastExceptionon fields or methods insidenew Expectations(){..}, and onOuter.this.mock.- Tests: every JMockit test runs the whole
JMockitToMockitocomposite (T/jmockit/JMockitTestUtils.java:39-46), on mockito-core 3.12, while the composite adds 5.x.
-
G5
EasyMockVerifyToMockitoVerify: stubs treated as expectations, bareverifyleft behindDetails
- Where:
M/easymock/EasyMockVerifyToMockitoVerify.java:61,67-70,121,125-129. - Problem (Confirmed):
andStubReturn,andStubThrowandandStubAnswerbecomeverify(dep).foo()checks that require exactly one call.- Some chains are never collected:
.times(2),.anyTimes(),.once(), expectations set in@Before, and multi-mockverify(a, b). These leaveverify(dep), which becomes a bareMockito.verify(dep);. That throwsUnfinishedVerificationException, or doesn't compile with two mocks. - The single-static
import org.mockito.Mockito.verifyshadowsEasyMock.*. The test atT/easymock/EasyMockVerifyToMockitoVerifyTest.java:271-327expects exactly this.
- Decision: what should happen to a
verify(dep)that can't be translated?
- Where:
-
G6 EasyMock leftovers that don't compile (related: EasyMock to Mockito missing
expectLastCallconversion #738)Details
- PROBED:
RemoveExtendsEasyMockSupport(M/easymock/RemoveExtendsEasyMockSupport.java:45), run throughEasyMockToMockito, leavesRunnable r = createMock(Runnable.class);unresolved. InheritedcreateMock,mock,niceMock,strictMock,resetAllandcreateMockBuilderhave no mapping.isAssignableToalso strips a user base class that extendsEasyMockSupport. - Confirmed,
Y/easymock.yml::81-86:IMocksControl createMock(..)becomesMockito.createMock(..), which doesn't exist, and the later rename can't match.:93-95:mock(String, Class)becomes aMockito.mock(String, Class)that doesn't exist.capture(..)becomesargThat, althoughCaptureisn't anArgumentMatcher.- The
RemoveDependencyis unconditional.
- None of these is tested.
- PROBED:
H. TestNG bugs
-
H1
TestNgAssertThrowsToAssertThatgarbles the message overloads (PROBED)Details
- Where:
M/testng/TestNgAssertThrowsToAssertThat.java:87-95. - Problem:
assertThrows("msg", ISE.class, () -> ..)becomesassertThatExceptionOfType("msg").isThrownBy(ISE.class), and the lambda is lost. - Fix: handle 3 arguments by mapping the message to
.as(..), or bail out.
- Where:
-
H2 TestNG deltas read as messages, dropped, or of the wrong type
Details
- Integer delta read as a message (Confirmed):
TestNgAsserts.isFloatingPointType(M/testng/TestNgAsserts.java:31-40) inspects the argument, not the parameter, soassertEquals(d, 1.0, 0)becomes.as(0). The same pattern is in:TestNgAssertEqualsToAssertThat:91,152TestNgAssertNotEqualsToAssertThat:75TestNgAssertionToAssertJ:131TestNgSoftAssertToAssertJ:124
- Integral delta dropped (Confirmed):
TestNgAssertEqualsToAssertThat:163-169,180-186,TestNgAssertNotEqualsToAssertThat:83-88,99-104. This is the same class of bug as C1. - Float (Suspected):
isCloseTo(.., within(0.001))passes anOffset<Double>to a float assertion. - Fix: read
getMethodType().getParameterTypes().get(2). There are no delta tests.
- Integer delta read as a message (Confirmed):
-
H3 TestNG
SoftAssert/Assertionrecipes skip nested calls and aren't self-containedDetails
- Where:
M/testng/TestNgSoftAssertToAssertJ.java:51-54,77,83andTestNgAssertionToAssertJ.java:54-57. - Problem (Confirmed): these return without calling
super, soxs.forEach(x -> softly.assertTrue(x > 0))is never migrated. The YAMLChangeType(Y/testng.yml:53-55) then retypessoftlytoSoftAssertions, which has noassertTrue. - Not self-contained: run on its own, the recipe emits
.assertThat(..)on a TestNGSoftAssert. - Fix: move the
ChangeTypeintogetRecipeList(). - Tests: there are no dedicated tests for
TestNgAssertNotEqualsToAssertThatorTestNgSoftAssertToAssertJ, and every TestNG test uses an unversionedclasspath("testng").
- Where:
I. Testcontainers, WireMock, Arquillian & DBRider bugs
-
I1
ReplaceContainerImageName: invalid Vault image, and an over-broad prefixDetails
- Vault (Confirmed):
M/testcontainers/ReplaceContainerImageName.java:88-89appendsvalue.substring(imagePrefix.length()), and the Vault entry's prefix (Y/testcontainers.yml:282-285) already contains the colon. So"vault:1.13.3"becomes"hashicorp/vault1.13.3". There is no Vault test. - Prefix (Confirmed,
:69,73-86):startsWith("confluentinc/cp-kafka")also matchescp-kafka-rest.- Any
DockerImageName.parsein a file that constructs aKafkaContaineris rewritten, even when it feeds another container.
- Fix: require a
:,@or end boundary after the prefix, put back the separator, and check the enclosing constructor. - Tests: they use
dependsOnstubs even though the Testcontainers jars are on the classpath (alsoMigrateToKafkaNativeTest:34,MigrateToOracleFreeTest:35).
- Vault (Confirmed):
-
I2
AddTestcontainersAnnotationshalf-migrates JUnit 4 testsDetails
- Where:
M/testcontainers/AddTestcontainersAnnotations.java:53-58,62. - Problem (Confirmed):
@Testcontainersis added next toorg.junit.Test, and JUnit 4 ignores the extension. Notestcontainers-junit-jupiterdependency is added either. - Also: the outer class is annotated when only a nested class changed.
- Fix: gate on Jupiter usage, and add an
AddDependencywithonlyIfUsing.
- Where:
-
I3 Smaller Testcontainers items
Details
ConvertToRawType:73(Confirmed):(J.Identifier) pt.getClazz()throws on fully qualified types. Suspected:visitTypedoesn't recurse into type arguments (:45-53).- Missing relocations (Confirmed against the 2.0.5 jars):
containers.TrinoContainer(onlyConvertToRawTypeexists,Y/testcontainers.yml:761-762) andcontainers.ClickHouseContainer. - Suspected:
dynalitehas no 2.x artifact (:617).MigrateToKafkaNativeandMigrateToOracleFreerun on their own need 1.19.x but bump nothing.
-
I4
ReplaceSettersWithTransform: lost mutations and captured localsDetails
- Where:
M/wiremock/ReplaceSettersWithTransform.java:199-207,233-244. - Dead stores (Confirmed): parameters and for-each variables get
m = m.transform(..), so the caller's stub is unchanged. The tests enshrine this (T/wiremock/ReplaceSettersWithTransformTest.java:69-130,161-185,223-245). - Captured locals (Confirmed): a local captured by a lambda elsewhere in the method loses effective finality, so the code no longer compiles.
- Decision: bail out with a TODO, or keep today's silent rewrite.
- Where:
-
I5 Smaller WireMock, Arquillian and DBRider items
Details
ReplaceArquillianInSequenceAnnotation(Confirmed,:51): a bare@InSequencebecomes@Order, which has no default value. Also,:51queues theChangeTypeonce per class, and:61queues one visitor per method.ExecutionListenerToDbRiderAnnotation(Confirmed,:119,139-141): skips a non-arrayvalue = X.classand classes that already have@DBRider, but the composite still swaps the dependency, so the listener reference breaks.shouldAddDbRiderAnnotation()is dead code.ReplaceRemovedConstructors(Confirmed,:240,250-258): aJavaTemplateis cached in a static field, which races and pins the first run's classpath.RemoveDuplicateContentTypeStubHeader:81-89(Suspected): also edits webhook headers.RemoveDuplicateContentTypeHeader:86-98(Suspected): misses chained duplicates.M/wiremock/has nopackage-info.javawith@NullMarked.
Sections J–O (dependencies & build files, crash-proofing, types, preconditions, mechanical polish, open questions) continue in the first comment, because the full list exceeds GitHub's issue size limit.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsBacklog
Activity
timtebeek commentedon Oct 24, 2026
J. Dependencies & build files
J1 Support Gradle Kotlin DSL in
GradleUseJunitJupiterandAddJupiterDependenciesDetails
M/junit5/GradleUseJunitJupiter.java:62,89-91,M/junit5/AddJupiterDependencies.java:87,123.GroovyIsoVisitor, andGradleUseJunitJupiteralso requires the path to end inbuild.gradle.build.gradle.ktsprojects therefore get neitheruseJUnitPlatform()norjunit-jupiter, and their tests silently stop being discovered. There is nobuildGradleKtstest anywhere in the repo.:54-58are dead matchers.useJUnit { includeCategories 'x' }isn't matched (:131).J2 Key
AddDependencyon pre-migration typesDetails
Y/jmockit.yml:51-62(mockito-coreandmockito-junit-jupiter,onlyIfUsing: org.mockito.*) andY/assertj.yml:549-554(assertj-core, keyed onorg.junit.jupiter.api.Assertions).AddDependencyscans the pre-edit sources, and the CLI and platform run one cycle. JMockit removesjmockitunconditionally (:63-65) and never adds Mockito.JUnitToAssertjon a JUnit 4 project gets no AssertJ dependency.Y/wiremock4.yml:289-290already documents the right pattern.mavenProjectwith onlyorg.jmockit:jmockitplus an@Mockedfield, run throughJMockitToMockito.onlyIfUsing: mockit..*andorg.junit.Assert.J3
JUnit5to6MigrationgatingDetails
Y/junit6.yml:49-50,75-81,101-105.HasMinimumJavaVersiongate never passesbuild.gradle(.kts)(rewrite#9045 fixed sources, not build scripts). Java moves to 6.x APIs while the Gradle dependencies stay on 5.x, and the Gradle-onlyjunit-platform-launcherentry is dead.T/junit6/JUnit5to6MigrationTest.java:137only has a Gradle no-change test.MigrateJUnitPioneerToJupiter,engine.Constants→api.Constants) run even when Spring Boot 4.0.x manages 6.0.1. Probe: aspring-boot-starter-parent4.0.5 project with Pioneer@DefaultLocale.J4 Smaller dependency items
Details
Y/junit-pioneer.yml:33-59(Confirmed):ReadsSystemPropertyandWritesSystemPropertyare missing, although the description (:22-23) claims they are covered.UpgradeSurefirePlugin(Y/junit5.yml:404-413, Suspected): addsjunit-platform-surefire-provider:1.1.0to surefire 3.2.5. The description (:397) says "latest" but pins 3.2.5.Y/junit5.yml:514-516: renamesaddResourceContainerSelectorResolverto itself.K. Crash-proofing: unchecked casts and missing-type NPEs
K1 Replace unchecked casts on user-code shapes with instanceof guards and bail-outs
Details
ParameterizedRunnerToParameterized:118-124((Integer) (J.Literal))CategoryToTag:93-94(J.FieldAccess.class::cast)HamcrestInstanceOfToJUnit5:72SetupStatementsRewriter:59,74,153ConvertToRawType:73MockitoWhenOnStaticToMockStatic:387-394JMockitMockUpToMockito:100,201,215,230,497(Suspected)K2 Null-check types and bodies before use
Details
RemoveNotMatcherVisitor:47-48,56HamcrestMatcherToJUnit5:163HamcrestNotMatcherToAssertJ:156-158JMockitMockUpToMockito:335,361TestMethodsShouldBeVoid:76MinimumJreConditions:321-325(parseInt)AdoptAssertJDurationAssertions:170(toIntExact)ReplaceMockitoTestExecutionListener:205(Kotlin)L. Type attribution &
TypeValidationL1 Don't hash
JavaType.VariableDetails
JavaType.Variableoverridesequals(name and owner) but nothashCode, soHashSetandHashMaplookups only work by identity. Key them onowner#name, asWireMockRuleToWireMockServer.keyalready does.SystemRulesToSystemStubs:123-124ExpectedSystemExitToCatchSystemExit:56-57,313-314,335,342RemoveDoNothingForDefaultMocks:63,116-137L2 Remove
TypeValidationrelaxations from testsDetails
TypeValidation.none()calls in 15 test classes, and 27 classes that settypeValidationOptions.ReplacePowerMockitoIntegrationTest:68-72turns off identifiers, method invocations and acyclic cursors for all 96 tests (with a TODO).PowerMockitoMockStaticToMockitoTest:53usescursorAcyclic(false), which points to a cursor bug.JUnit*ToAssertThatTestclasses, dating from 2022. They hideJUnitAssertTrue:67, which passes aSupplierthrough#{any(String)}.immutableExecutionContext(false)calls behind E1.ParameterizedRunnerToParameterizedTest(7) andTemporaryFolderToTempDirTest(2).CloseUnclosedStaticMocksTest(9 ×identifiers(false), "TODO Remove escape hatch").MockitoWhenOnStaticToMockStaticTest:75and:2283-2512.AssertToAssertionsTest:47, which is on the@DocumentExample.SimplifyAssertJEqualityAssertionTest:453(the unknown type is its subject) andAssertThrowsOnLastStatementTest:1204.L3 Keep method types correct
Details
TemporaryFolderToTempDir:271puts the class type on a method-name identifier.UseAssertSame:94-106uses a stub fallback.PowerMockitoDoStubbingToMockitoleaves a null method type on rewritten calls.ThenThrowCheckedExceptionToRuntimeException:75-77keeps a stale type and uses aShallowClass.M. Preconditions
M1 Add missing preconditions(AddUsesMethodandUsesTypepreconditions to recipes that visit every file #1227)Details
Reuse each visitor's own static matcher with
UsesMethod:AssertTrueInstanceofToAssertInstanceOf:47CleanupKotlinJUnit5AssertionImports:53AssertEqualsBooleanToAssertBoolean:42AssertFalseNegationToAssertTrue:41AssertNotEqualsBooleanToAssertBoolean:42JUnitTryFailToAssertThatThrownBy:50-51(visits everytry)SimplifyAssertJAssertion:70(8 YAML instances)SimplifyChainedAssertJAssertion:97(about 63 YAML instances)MockConstructionToTryWithResources:59-60AssertThatBooleanToAssertJ:39MinimumJreConditions:72-73M2 Fix preconditions that are too broad or too narrow(Tighten preconditions that were too broad or too narrow #1229)Details
AdoptAssertJDurationAssertions:89-98gates onAbstractLongAssert isEqualTo, which almost every AssertJ file uses. Tie it tojava.time.Duration.RemoveIsMatcher:40should gate on the rarerIS_MATCHER.TestMethodsShouldBeVoid:36usesorg..*Test.KotlinTestMethodsShouldReturnUnit:45usesorg.junit..*Test*, while its matcher allowsorg..*, so TestNG Kotlin tests are never visited.UsesType:testcontainers.yml:622-762,wiremock4.yml:78-217,truth.yml,easymock.yml.M3 Replace(UseisAcceptableoverrides with preconditionsJavaFileCheckerpreconditions instead ofisAcceptableoverrides #1219)Details
SystemPropertyRulesToPioneer:68-70SystemRulesToSystemStubs:127-129AssertThrowsOnLastStatementExpectedSystemExitToCatchSystemExitJUnitSoftAssertionsToSoftAssertionsExtensionN. Mechanical polish (low risk, good first issues)
N1 Typed
#{any(..)}placeholders instead of#{any()}Details
ReplaceRemovedConstructors(about 35)Assert*recipes (TestNgAssertionToAssertJ13,TestNgAssertEqualsToAssertThat12)JUnitAssertEqualsToAssertThat(9)SimplifyArrayLengthAssertion(8)TruthThrowableAssertions(7)MockitoWhenOnStaticToMockStatic(6)#{}atJUnitAssertArrayEqualsToAssertThat:92.MigrateRequestMethodIsOneOf:74varargs pass-through.N2 Drop forced(Drop forcedmaybeAddImport(x, false)and give templates a classpath insteadmaybeAddImport(.., false)where templates already attribute the added types #1234)Details
JUnit*ToAssertThat(16 in 10 files), JMockit (8),AssertThrowsOnLastStatement:199,202,209,MockitoWhenOnStaticToMockStatic:112,173,301.WhiteboxToReflectionVisitorcases, which carry a justification.N3 Audit(DropcontextSensitive()contextSensitive()from self-containedJavaTemplates #1232)Details
N4 Unconditional sub-recipes:(Run unconditional sub-recipes fromdoAfterVisit→getRecipeList(); no whole-file side effectsgetRecipeList()and keep cleanups local to the changed element #1230)Details
doAfterVisit:MigrateJUnitTestCase:82-88TestRuleToTestInfo:61-72UpdateBeforeAfterAnnotations:61-67UseWiremockExtension:44-49RemoveObsoleteRunners:51-58(could be declarativeRemoveAnnotation)ReplaceArquillianInSequenceAnnotation:51AnyToNullable:61-65MockUtilsToStatic:59(Java-only, throughvisitCompilationUnit)AssertThrowsOnLastStatement:72andJMockitMockUpToMockito:305(LambdaBlockToExpression)JUnitFailToAssertJFail:84-85andRemoveDoNothingForDefaultMocks:162(RemoveUnusedImports)AssertToAssertions:86,145,JUnitParamsRunnerToParameterized:219,ExpectedExceptionToAssertThrows:95.ChangeMethodTargetToStatic(.., matchUnknownTypes = true):AssertToAssertions:87,146.N5 Use the(Use theCommentstrait instead of a hand-builtTextComment, andListUtils/reduceinstead of manual list handlingCommentstrait andListUtils/reduceinstead of hand-built comments and manual list handling #1226)Details
TextComment:MigrateJUnitTestCase:115SystemRules:146-147JUnitParamsRunnerToParameterized:124PowerMockitoDoStubbingToMockito:88-92DisableUnsupportedPowerMockTestsExpectedSystemExitToCatchSystemExit:197ExpectedExceptionToAssertThrows:200,225,255RemoveTestPrefix:165-176,TestsShouldIncludeAssertions:157-169.ListUtils.mapreturning null:UpdateTestAnnotation:202-213.AddMissingNested:114.N6(Hoist matchers created per visit toprivate static final UPPER_SNAKEmatchers; no per-visit matcher allocationprivate static finalconstants #1225)Details
AssertTrueInstanceofToAssertInstanceOf:52-53EnclosedToNested:47LifecycleNonPrivate:61-62AddMissingNested:119UpdateBeforeAfterAnnotations:77-78UseWiremockExtension:31AssertionsArgumentOrder:41-82,112HamcrestNotMatcherToAssertJ:68-71HamcrestMatcherToAssertJ:72-77,146RemoveTimesZeroAndOne:39-40MockitoJUnitRunnerToExtension:56-59,73ReplaceInitMockToOpenMock:107ReplaceMockitoTestExecutionListener:248MockitoWhenOnStaticToMockStatic:245-246,283ReplaceArquillianInSequenceAnnotation:43-47N7(UseMethodMatcher/AnnotationMatcherinstead of name stringsMethodMatcher,AnnotationMatcherand variable types instead of name strings #1228)Details
MigrateJUnitTestCase:101,125,174ParameterizedRunnerToParameterized:381AssertToAssertions:94-97,157-160MockitoUtils.getFirstTestMethod:124-127WhiteboxToReflectionVisitor:824HamcrestMatcherToJUnit5:172(valueOfplus a catch as control flow)hasJUnit5MethodAnnotationchains inRemoveTestPrefix:188,SimplifyTestThrows:100andTestMethodsShouldBeVoid:102. Use anAnnotationMatcher, asTestsShouldNotBePublicdoes.ArgumentMatchersRewriter:110,278,JMockitBlockRewriter:127,132,SetupStatementsRewriter:48,183,211,EasyMockVerifyToMockitoVerify:121.N8 Display names and descriptions(Fix typos, over-claiming and inconsistent recipe display names and descriptions #1231)Details
AssumeNotNullToAssumptions:36-37)@ExtendsWith(RemoveObsoleteRunners:36,38)@Parameterizedinstead of@ParameterizedTest(JUnitParamsRunnerToParameterized:63)by" (IsEqualToIgnoringMillisToIsCloseTo)AssertTrueComparison:38)TempDirNonFinal:36,39,LifecycleNonPrivate:50,53)AssertEqualsIntegralDelta:43JUnitAssertArrayEquals:45(sayscontains())RemoveExtendsEasyMockSupport:33-36JMockitMockUpToMockito:55PowerMockitoDoStubbingToMockito:47-49AssertThatBooleanToAssertJ:31HamcrestOfMatchersToAssertJ:39-42Mockito4to5Only(A1)Y/junit5.yml:419,468("all deprecated APIs")AssertJ*Rules("Adopt AssertJ Double Assertions")ReplaceArquillianInSequenceAnnotationY/arquillian.yml:20Y/junit5.yml:307,356,378,396Y/testcontainers.yml:206,624Y/mockito.yml:38,76,99AssertToAssertions:38,ExpectedExceptionToAssertThrows:58andUpdateMockWebServer:56AssertTrueInstanceofToAssertInstanceOf:43HandleExternalResourceRules:54,57TestRuleToTestInfo:39RemoveDuplicateTestTemplates:34,37SimplifySequencedCollectionAssertions:56JUnitFail:40cleanup.yml:21,junit-jupiter.yml:21,junit5.yml:21,junit6.yml:21)dbrider.yml:21,easymock.yml:21,jmockit.yml:21,MinimumJreConditions:69)Y/junit5.yml:186andY/powermockito.yml:22HamcrestMatcherToAssertJ:40andHamcrestNotMatcherToAssertJ:43say "migrate to JUnit5".SystemPropertyRulesToPioneer:62SimplifyMockitoVerifyWhenGivenassertjtag:Y/hamcrest.yml:27,49category.yml:26-45N9 Remove stale(Remove stale@Disabledtests,//noinspectionlines and restating comments or Javadoc@Disabledtests,//noinspectionlines and restating comments #1233)Details
@Disabled:UpdateBeforeAfterAnnotationsTest:180(rewrite#150 is closed)AssumeNotNullToAssumptionsTest:65(Invalid rewrite ofAssume.assumeNotNulltoAssumptions.assumeNotNull#886 is closed; see B7)JMockitExpectationsToMockitoTest:1611AddMissingNestedTest:622,643(blocked onKotlinFileChecker; see Add Kotlin support across the JUnit 4 to Jupiter migration #1189)//noinspection: 11 lines, e.g.AssertionsArgumentOrder:93,KotlinTestMethodsShouldReturnUnit:122,RemoveEmptyTests:63.ExpectedExceptionToAssertThrows:43-54HandleExternalResourceRules:37-42JUnitParamsRunnerToParameterized:35-43CleanupKotlinJUnit5AssertionImports:30-39MockitoJUnitToMockitoExtension:44-57AnyStringToNullable:32-34MockConstructionToTryWithResources:41-44CloseUnclosedStaticMocks:41-46EnvironmentVariablesCsvSourceToValueSourceTruthThrowableAssertions:67-157JMockitAnnotatedArgumentToMockitoReplaceSettersWithTransform:129,154,268AddTestcontainersAnnotations:73,92,100RemoveTestPrefixTestMethodsShouldBeVoidFIXMEs:RemoveNotMatcherVisitor:67,72(E1).@NonNull:EnvironmentVariables:55-114.N10 Test naming and structure(Align test names with recipes, group no-change tests and tidy classpath entries and YAML #1235)Details
AddMockitoJupiterForRulesis tested inMockitoRuleDependencyTest.MockitoBestPracticesTest:41loadsMockito1to5Migration.spec.recipeidentical todefaults():AssertEqualsBooleanToAssertBooleanTest:113.@Nested class NoChange: across Testcontainers, WireMock, Arquillian and DBRider.classpath("testng")andAddJupiterDependenciesTest:47.hamcrest-3inTimeoutRuleToClassAnnotation:111andJUnitParamsRunnerToParameterized:274,283,299, andjunit-4inAssertTrueInstanceofToAssertInstanceOf:104.matchOverrides: false(the default) atY/junit6.yml:109---atY/dbrider.yml:35Y/assertj.yml:18,23,596-600O. Open questions & coverage
O1 Codify
SingletonplacementDetails
Singletonsits on almost every declarative recipe, whereas the checklist says top recipe only. It is harmless and dedupes, so we probably want to codify the repo's choice in the checklist.O2 Version-pair names
Details
Mockito4to5Only,JUnit5to6MigrationandArchUnit0to1Migrationgo against the naming rule, but renaming them breaks users. Keep them?O3
RepositoryHasDependencyinY/powermockito.yml:130,154Details
ModuleHasDependency. This looks deliberate for parent POMs; confirm and add a comment.O4 Test the declarative recipes and entries that ship untested
Details
cleanup.BestPractices).T/:UpgradeToJUnit513constants: 7Assertj(J2)asList(E7)IMocksControlandcapture(G6)KafkaContainer(A5)HandleExternalResourceRules(A2)Mockito4to5Only(A1)CleanupPowerMockImports,ModuleDoesNotDeclareJUnit4,TestNgAssertNotEqualsToAssertThat,TestNgSoftAssertToAssertJ.JMockitBlockToMockito,JMockitMockUpToMockitoandJMockitAnnotatedArgumentToMockitoare only tested through the composite.O5 Kotlin coverage outside the JUnit 4 migration (related: JUnit4to5Migration - undiscovered or invalid Kotlin tests #1188)
Details
T/assertj/,T/wiremock/orT/testcontainers/, and none for the AssertJ Hamcrest recipes.O6 JMockit semantics that change silently
Details
Expectationswith only a result lose their "at least once" check.anyStringand(T) anymatch null, while Mockito'sanyString()andanyList()don't.@Mockedparameters mock every instance, whileMockito.mockmocks one.@Capturing,Delegate(Convert JMockit@TestedandDeencapsulationwhen migrating to Mockito #522),withCaptureand static expectations on@Mockedtypes. Decide whether to bail out or migrate them.O7 AssertJ design questions
Details
PlaceAssertJDescriptionBeforeAssertion(:50) also moveswithFailMessageandoverridingErrorMessage. Once moved, they take effect and replace AssertJ's detailed message. Should it move onlyasanddescribedAs?JUnit*ToAssertThatrecipes are near-identical, and their delta handling has already drifted apart (D2). Consolidate them behind a shared helper?IsEqualToIgnoringMillisToIsCloseTo: keep it with a description that states the weaker semantics, or drop it fromAssertj?.as(..)(hand-written recipes) versus.withFailMessage(..)(Picnic Refaster), already flagged inY/testng.yml:36-41.O8 Remove the
immutableExecutionContext(false)escape hatch from the repoDetails