Skip to content

Expand TestNG to AssertJ assertion coverage#1028

Merged
timtebeek merged 3 commits into
openrewrite:mainfrom
juherr:testng-assertj-assertion-coverage
Jun 19, 2026
Merged

Expand TestNG to AssertJ assertion coverage#1028
timtebeek merged 3 commits into
openrewrite:mainfrom
juherr:testng-assertj-assertion-coverage

Conversation

@juherr

@juherr juherr commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

What's changed

Widen org.openrewrite.java.testing.testng.TestNgToAssertj from "scalar assertEquals/assertNotEquals + Refaster basics" to full coverage of every static method on org.testng.Assert (verified against TestNG 7.12.0).

New recipes

Recipe TestNG → AssertJ
TestNgAssertSameToAssertThat assertSame(a, b)assertThat(a).isSameAs(b)
TestNgAssertNotSameToAssertThat assertNotSame(a, b)assertThat(a).isNotSameAs(b)
TestNgAssertThrowsToAssertThat assertThrows(Type.class, run)/expectThrows(...)assertThatExceptionOfType(Type.class).isThrownBy(run) (.actual() when the result is used); assertThrows(run)assertThatThrownBy(run)
TestNgAssertEqualsNoOrderToAssertThat assertEqualsNoOrder(...)containsExactlyInAnyOrder / containsExactlyInAnyOrderElementsOf (arrays, collections, iterators)
TestNgAssertEqualsDeepToAssertThat assertEqualsDeep/assertNotEqualsDeep (Map/Set) → usingRecursiveComparison().isEqualTo()/.isNotEqualTo()
TestNgAssertListToAssertThat assertListContainsObject/assertListNotContainsObject.contains()/.doesNotContain(); predicate-based assertListContains/assertListNotContains.anyMatch()/.noneMatch()

Fix to existing assertEquals/assertNotEquals

TestNgAssertEqualsToAssertThat matched all assertEquals(..) overloads and always emitted isEqualTo(...) — wrong for the structural overloads (for Iterator/Iterable it compared object identity rather than TestNG's element-wise semantics). It now dispatches on the actual argument's type:

  • arrays → containsExactly
  • SetcontainsExactlyInAnyOrderElementsOf
  • other Iterable (List/Collection) → containsExactlyElementsOf
  • IteratorassertThat(actual).toIterable().containsExactlyElementsOf(() -> expected)
  • Map / scalars → isEqualTo (unchanged, already correct)

Both assertEquals and assertNotEquals also gained an integral-actual + floating-delta guard (mirroring JUnitAssertEqualsToAssertThat) so an int/long actual with a floating delta falls back to isEqualTo/isNotEqualTo instead of an uncompilable isCloseTo.

Coverage

After this PR, every static method on org.testng.Assert is handled:

  • Dedicated Java recipes: assertEquals, assertNotEquals, assertEqualsDeep, assertNotEqualsDeep, assertEqualsNoOrder, assertSame, assertNotSame, assertThrows, expectThrows, assertListContains(Object), assertListNotContains(Object).
  • Picnic Refaster rules (kept): assertTrue, assertFalse, assertNull, assertNotNull, fail.

Notes

  • All new recipes are registered in testng.yml and have a mirror *Test with a single @DocumentExample.
  • recipes.csv was regenerated via ./gradlew recipeCsvGenerate (not hand-edited).
  • The message argument maps to .as(...), consistent with the existing TestNG recipes. The known .as(...) vs .withFailMessage(...) divergence with the Picnic Refaster path is documented as future work in a comment in testng.yml rather than resolved here.

Test plan

  • ./gradlew build — green (license check, recipes.csv completeness, full test suite).
  • Includes regression coverage that assertEquals(List, List) now produces containsExactlyElementsOf rather than isEqualTo, and that the Iterator overloads of assertEquals/assertEqualsNoOrder produce compilable toIterable()-based assertions.

juherr added 2 commits June 19, 2026 13:40
Add dedicated recipes for assertSame/assertNotSame, assertThrows/expectThrows,
assertEqualsNoOrder, and assertEqualsDeep/assertNotEqualsDeep, and fix
assertEquals/assertNotEquals to use element-wise assertions (containsExactly,
containsExactlyElementsOf, toIterable) for arrays, collections, sets and
iterators instead of a reference-level isEqualTo. Each recipe is registered in
testng.yml and covered by a mirror test with a @DocumentExample.
Cover the remaining org.testng.Assert static assertions: assertListContainsObject
/assertListNotContainsObject map to assertThat(list).contains()/.doesNotContain(),
and the predicate-based assertListContains/assertListNotContains map to
.anyMatch()/.noneMatch().
…cipes

Call super.visitMethodInvocation() before matching so assertions nested
inside non-matching invocations (e.g. within a lambda argument) are still
visited and migrated. Brings the TestNG recipes and JUnitFailToAssertJFail
in line with the recursing pattern already used elsewhere.

@timtebeek timtebeek left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for your efforts here, especially considering where you're coming from! I've pushed one small change to handle nested cases too, but other than that the work looks solid here. Thanks for applying your in depth knowledge of mappings to complete the migration.

I hope all is well there; let me know if you'd like an expedited release to get this out. Otherwise our next scheduled release will be in two weeks.

@timtebeek timtebeek merged commit 665bd84 into openrewrite:main Jun 19, 2026
1 check passed
@github-project-automation github-project-automation Bot moved this from Ready to Review to Done in OpenRewrite Jun 19, 2026
@juherr

juherr commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

To be honest, Claude did most of the work here, and I expect it to be good enough.

We'll see how it holds up once the community starts testing it.

Please ping me when the release is available, and I'll add the corresponding documentation on the TestNG side.

@juherr juherr deleted the testng-assertj-assertion-coverage branch June 19, 2026 13:05
@timtebeek

Copy link
Copy Markdown
Member

To be honest, Claude did most of the work here, and I expect it to be good enough.

Hah no worries; I'd recognized the signatures already indeed, and knew what to look for in the review. Still helpful!

We'll see how it holds up once the community starts testing it.

Please ping me when the release is available, and I'll add the corresponding documentation on the TestNG side.

Sure! I've put out release right now that's already up at Maven Central:
https://github.com/openrewrite/rewrite-testing-frameworks/releases/tag/v3.39.0

Send a link when you have any messaging up that you'd like me to have a look over.

@timtebeek

Copy link
Copy Markdown
Member

Thanks for the continued reports and contributions! I've expanded coverage quite a bit today following your reports:

Let me know if you plan any further validation (potentially using our snapshot versions), before I cut another release to get these fixes out.

@juherr

juherr commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

Validated against the full TestNG suite using org.openrewrite.recipe:rewrite-testing-frameworks:3.40.0-SNAPSHOT (recipe org.openrewrite.java.testing.assertj.Assertj, OpenRewrite Gradle plugin 7.35.0, JDK 21) — we validate all the changes. 🎉

The four reported issues now transform exactly as expected:

Issue Before After (snapshot)
#1029 Assert.assertEquals("a", "b") assertThat("a").isEqualTo("b") + import static …assertThat
#1030 assertThat(x instanceof String).isTrue() / .isFalse() .isInstanceOf(String.class) / .isNotInstanceOf(String.class)
#1031 assertThat(4).isEqualTo(list.size()) assertThat(list).hasSize(4)
#1032 assertThat(v).isNotSameAs(0) / .isSameAs(1) .isNotZero() / .isOne() — in a single run ✅

Running it over our already-migrated suite produced only correct simplifications, no regressions — and the new rules even caught cases our manual pass had missed, e.g. array length:

assertThat(arr.length).isEqualTo(2);                  // -> assertThat(arr).hasSize(2);
assertThat(arr.length).isZero();                      // -> assertThat(arr).isEmpty();
assertThat(passedTests.length).isEqualTo(passed.size()); // -> assertThat(passedTests).hasSameSizeAs(passed);

Good to go for a release from our side. One item is out of scope for this repo and tracked upstream: isSameAs/isNotSameAs on reference types (e.g. two java.util.Date) is still rewritten to isEqualTo/isNotEqualTo by the bundled AssertJPrimitiveRulesPicnicSupermarket/error-prone-support#2277. Not part of these changes; just flagging it for whenever a newer error-prone-support is pulled in.

Thanks again for the quick turnaround on all of these!

@timtebeek

Copy link
Copy Markdown
Member

Perfect, thanks for confirming, and thanks again for the helpful reports!

I've just triggered the release, that should be out on Maven central in 30 minutes:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

assertj recipe Recipe request

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants