Skip to content

Migrate testng-core's internal tests from org.testng.Assert to AssertJ#3278

Draft
juherr wants to merge 4 commits into
testng-team:masterfrom
juherr:deprecate-assert-assertj
Draft

Migrate testng-core's internal tests from org.testng.Assert to AssertJ#3278
juherr wants to merge 4 commits into
testng-team:masterfrom
juherr:deprecate-assert-assertj

Conversation

@juherr

@juherr juherr commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary

This PR moves TestNG away from maintaining its own assertion library, as discussed in testng-team/testng-asserts#15 and
#3197. It deprecates org.testng.Assert, migrates TestNG's own tests to AssertJ, starts delegating
Assert to AssertJ where it is safe to do so, and turns testng-asserts into a separate, optional
published artifact.

The work is split into self-contained commits so it can be reviewed phase by phase.

Motivation

The OpenRewrite recipe org.openrewrite.java.testing.testng.TestNgToAssertj
(rewrite-testing-frameworks ≥ 3.39.0) now covers the whole Assert class, so users can migrate
automatically. That makes deprecation realistic.

What's in this PR

Commit Change
Deprecate org.testng.Assert @Deprecated(since = "7.13.0", forRemoval = true) + a migration guide (docs/MIGRATING_ASSERTIONS.md).
Migrate internal tests ~500 testng-core test files rewritten off org.testng.Assert to AssertJ via a one-shot OpenRewrite run (no plugin left in the build); assertj-core bumped 3.23.1 → 3.27.7.
Delegate behaviour-identical checks assertNull/assertNotNull/assertSame/assertNotSame now delegate to AssertJ.
Delegate assertThrows assertThrows now delegates to AssertJ; expectThrows is kept (it returns the caught exception).
Publish testng-asserts standalone Removed from the testng fat jar; published as org.testng:testng-asserts depending only on AssertJ.
Document OSGi limitation testng-asserts ships as a plain jar for now (see below).

Design decisions

⚠️ Packaging change for users

org.testng.Assert is no longer bundled in the org.testng:testng jar. To keep using it, add:

<dependency>
  <groupId>org.testng</groupId>
  <artifactId>testng-asserts</artifactId>
  <scope>test</scope>
</dependency>

which brings AssertJ transitively. The recommended path is to migrate to AssertJ (see
docs/MIGRATING_ASSERTIONS.md).

OSGi

testng-asserts is published as a plain jar, not an OSGi bundle: org.testng.Assert and
FileAssert share the org.testng package with the core classes exported by the testng bundle,
so a testng-asserts bundle re-exporting org.testng would create a split package. Proper OSGi
support needs dedicated design and is left as a follow-up.

Testing

  • :testng-asserts:test — 181 passed
  • :testng-core:test — 12474 passed
  • :testng-test-osgi:test — 4 passed
  • ./gradlew autostyleCheck — clean
  • Verified the merged testng jar no longer contains org/testng/Assert*, and the published
    testng-asserts POM depends only on org.assertj:assertj-core.

Follow-ups (not in this PR)

  • OSGi bundle metadata for testng-asserts (split-package handling).
  • Wider delegation / further deprecations (e.g. FileAssert, the OO Assertion/SoftAssert API).

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 624db884-2faa-4f1d-beff-a107f49ed90c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@juherr juherr force-pushed the deprecate-assert-assertj branch from b180e41 to 32ce21f Compare June 19, 2026 20:26
@juherr juherr force-pushed the deprecate-assert-assertj branch from 32ce21f to 1ac1eb7 Compare June 19, 2026 20:45
@juherr juherr force-pushed the deprecate-assert-assertj branch from e7e85d7 to 31f3139 Compare June 19, 2026 22:13
@juherr juherr force-pushed the deprecate-assert-assertj branch from 31f3139 to 6a0c87e Compare June 19, 2026 22:35
@juherr juherr force-pushed the deprecate-assert-assertj branch 2 times, most recently from 861c3e6 to dd73dd9 Compare June 19, 2026 23:21
@juherr juherr added Feature: assertions Assert / assertEquals and assertion utilities testng-api Public API changes / compatibility architecture Internal architecture / refactoring labels Jun 20, 2026
@juherr juherr force-pushed the deprecate-assert-assertj branch 3 times, most recently from 1d33167 to dcebefa Compare June 20, 2026 07:35
@juherr juherr marked this pull request as ready for review June 20, 2026 08:03
@juherr juherr requested a review from krmahadevan as a code owner June 20, 2026 08:03
@juherr

juherr commented Jun 21, 2026

Copy link
Copy Markdown
Member Author

@krmahadevan Sorry for the huge diff. I used openrewrite and claude to make it.
I also checked all changes one by one but another eye and confirmation will help 🙏

@krmahadevan

Copy link
Copy Markdown
Member

@krmahadevan Sorry for the huge diff. I used openrewrite and claude to make it. I also checked all changes one by one but another eye and confirmation will help 🙏

@juherr - No issues. I will go through this asap.

@juherr

juherr commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

@krmahadevan Before we wrap this up, I'd like your input on how we handle the assertion issues and the overall sequencing, because it reframes this PR.

There are ~10 open tickets about org.testng.Assert (e.g. testng-team/testng-asserts#8, testng-team/testng#543, testng-team/testng-asserts#10, testng-team/testng-asserts#11, testng-team/testng-asserts#13, testng-team/testng-asserts#14, testng-team/testng-asserts#16, testng-team/testng-asserts#17, testng-team/testng-asserts#18). Most are requests to add assertions or change comparison/message semantics — things we've decided not to do in core anymore (#2649, #3197).

Rather than closing them all as wontfix, I'd like to follow the testng-ant precedent: move testng-asserts into its own repository that acts as a community-owned home. TestNG core stays focused on the engine, and anyone who still cares about the assertion API can pick it up there. I'd then transfer the assertion issues to that repo instead of closing them — they stay actionable for whoever maintains the library.

If we go that way, I'd rather split this work into two phases instead of doing everything here:

Phase 1 — extract the module (this PR, trimmed down):

  • Move testng-asserts out of the testng fat jar and publish it as the standalone org.testng:testng-asserts artifact.
  • Deprecate org.testng.Assert in core with a plain @Deprecated (not forRemoval = true): it isn't disappearing, it's moving to its own (potentially community-maintained) home. Javadoc recommends AssertJ and points to the standalone artifact.
  • Migration guide reworded from "will be removed" → "no longer maintained in core, available standalone, AssertJ recommended".
  • Extract into a dedicated repo (à la testng-ant) and transfer the assertion issues there.

Phase 2 — AssertJ implementation, in the new repo:

  • The AssertJ delegation (assertNull/NotNull/Same/NotSame, assertThrows, …) and any further work happen in the new testng-asserts repo, decoupled from the core release cycle.

This keeps core's change minimal and reversible, and avoids baking an AssertJ dependency / behaviour change into core right before the module leaves anyway.

WDYT? Mainly want to align on: (a) hand-off vs. removal, (b) dropping forRemoval, and (c) splitting extraction (Phase 1) from the AssertJ work (Phase 2).

@krmahadevan

Copy link
Copy Markdown
Member

@juherr - I think your proposal of 2 phased approach makes a lot more sense. Although when compared to testng-ant, assertion library is more of a redundancy. But I agree with you, that people may just not want to take up all that refactoring and would perhaps want to still to use whatever is already there and maybe for newer stuff they may want to use AssertJ. In any case, I dont think we should make that decision for our users and instead give them the option to take it forward.

@juherr

juherr commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

@krmahadevan Thanks, that makes sense.

To keep things moving faster, I would actually prefer starting directly with the dedicated testng-asserts repository and publishing the standalone artifact from there, instead of producing the first standalone release from the main TestNG repository.

My proposed sequence would be:

  1. Create a dedicated testng-asserts repository under testng-team.
  2. Move / import the current assertion code there.
  3. Set up the build and Maven Central publication for org.testng:testng-asserts from that repository.
  4. Publish the first standalone version from the dedicated repository.
  5. Then update the main TestNG repository to:
    • stop embedding the assertion classes in the main org.testng:testng artifact;
    • keep the deprecated compatibility surface where needed;
    • document the migration path to org.testng:testng-asserts;
    • recommend AssertJ for new code or larger refactoring efforts.

That would avoid doing the release setup twice: once in the main repository, then again in the dedicated repository.

But I am fine either way. I just wanted to propose this approach because I think it may be faster and cleaner from an ownership/release perspective.

I'll let you decide which path you prefer:

  • first standalone release from the main TestNG repository, then move to testng-asserts;
  • or create testng-asserts first and publish directly from there.

@krmahadevan

Copy link
Copy Markdown
Member

@juherr - That sounds good. Lets start with Create a dedicated testng-asserts repository under testng-team. and then work backwards to replace what exists within TestNG with this dependency.

@juherr juherr force-pushed the deprecate-assert-assertj branch from 6840777 to 86ae44e Compare June 28, 2026 18:01
@juherr juherr marked this pull request as draft June 28, 2026 18:02
@juherr juherr changed the title Deprecate org.testng.Assert and make testng-asserts an optional, AssertJ-backed artifact Migrate testng-core's internal tests from org.testng.Assert to AssertJ Jun 28, 2026
juherr added 3 commits June 28, 2026 20:11
testng-core's own tests are migrated off org.testng.Assert to AssertJ
using the OpenRewrite recipe TestNgToAssertj (rewrite-testing-frameworks
3.39.0), run as a one-shot -- the temporary plugin config is not retained.

- testng-core/src/test: ~500 files rewritten to org.assertj.core.api.Assertions
- testng.testing convention: bump assertj-core 3.23.1 -> 3.27.7
  (the recipe emits the no-arg Assertions.fail() added in AssertJ 3.26.0)
- ParameterConstructorTest: fix float offset (Offset.offset(0.1f))
- BasicSample.kt: kept on org.testng.Assert (OpenRewrite Kotlin support is
  limited; the migrated AssertJ no-arg fail() breaks Kotlin type inference)

A few testng-core tests exercising org.testng.Assert behaviour directly
(DependentTest, AssertionsTestSample.groovy) intentionally keep using it.
The TestNgToAssertj recipe only ports the API: it turns Assert.assertX(cond)
into assertThat(cond).isTrue(), so predicate expressions kept the original
boolean shape and produced unhelpful "expected true but was false" failures.

Run the AssertJ best-practices cleanup (the org.openrewrite.java.testing.assertj.Assertj
aggregate, minus the cross-framework migrators so the intentional JUnit-interop
fixtures are left untouched) as a one-shot over testng-core's tests. This rewrites
predicates to dedicated assertions with proper failure messages, e.g.:

  assertThat(list.size()).isEqualTo(3)        -> assertThat(list).hasSize(3)
  assertThat(time < 2000).isTrue()            -> assertThat(time).isLessThan(2000)
  assertThat(c.isEmpty()).isFalse()           -> assertThat(c).isNotEmpty()
  assertThat(s.contains(x)).isTrue()          -> assertThat(s).contains(x)
  assertThat(n).isEqualTo(0)                  -> assertThat(n).isZero()

instanceof predicates are not handled by any existing recipe (tracked upstream as
openrewrite/rewrite-testing-frameworks#1030), so they were converted by hand:

  assertThat(o instanceof T).isTrue()         -> assertThat(o).isInstanceOf(T.class)

A few assertions the recipes can't reach were also tidied by hand (e.g. in GraphTest):
reversed actual/expected and boolean-OR equality chains:

  assertThat(n).isEqualTo(x.size())           -> assertThat(x).hasSize(n)
  assertThat(a.equals(x) || b.equals(x))      -> assertThat(x).isIn(a, b)
      .isTrue()

One assertion was fixed by hand too: the recipe rewrote `null != x` to the ambiguous
`assertThat(null).isNotSameAs(x)` (tracked as PicnicSupermarket/error-prone-support#2276);
replaced with `assertThat(x).isNotNull()`.
Idiomatic simplifications surfaced while validating the
rewrite-testing-frameworks 3.40.0 snapshot against the suite, which the
earlier hand-finishing pass had not covered:

- array length: assertThat(arr.length).isEqualTo(n) -> assertThat(arr).hasSize(n),
  assertThat(arr.length).isZero() -> assertThat(arr).isEmpty()
- assertThat(a.length).isEqualTo(b.size()) -> assertThat(a).hasSameSizeAs(b)
- assertThat(x).isNotEqualTo(0) / isEqualTo(0) -> isNotZero() / isZero()

Behaviour-preserving; full testng-core suite green (12474 passed, 0 failed).
@juherr juherr force-pushed the deprecate-assert-assertj branch from 86ae44e to 54300a2 Compare June 28, 2026 18:19
With testng-core's own tests fully on AssertJ, the last org.testng.Assert /
org.testng.asserts usages are migrated and the testng-asserts dependency is
dropped from both modules:

- DependentTest: Assert.assertEquals -> assertThat(...).isEqualTo(...)
- issue2400/IssueTest, test/tmp/A: org.testng.asserts.SoftAssert ->
  org.assertj.core.api.SoftAssertions
- BasicSample.kt: org.testng.Assert.fail -> AssertJ fail<Nothing>("...")
- AssertionsTestSample.groovy (issue2854): org.testng.Assert.assertEquals ->
  AssertJ assertThat (the org.testng.Assert Groovy-overload coverage now lives
  in the standalone testng-asserts repo, GITHUB-3034)
- testng-core: drop testImplementation(projects.testngAsserts)
- testng-api: drop api(projects.testngAsserts)

org.testng.AssertJUnit (JUnit-style assertions used by the internal
ComparisonCriteria) stays in testng-core and is unaffected. testng-asserts is
still built and embedded in the org.testng:testng fat jar; that and the BOM
retarget the standalone org.testng:testng-asserts once it is published.
krmahadevan pushed a commit to testng-team/testng-asserts that referenced this pull request Jun 30, 2026
#20)

* Delegate behaviour-identical assertions to AssertJ

Port the AssertJ-backed assertion implementation from
testng-team/testng#3278 into this standalone artifact, so it can be
dropped from the parent PR.

- Assert: delegate assertNull/assertNotNull/assertSame/assertNotSame and
  assertThrows to AssertJ (behaviour-identical, only the failure message
  differs); remove the now-unused failSame/failNotSame helpers. Equality
  comparison intentionally keeps TestNG semantics (documented NOTE).
- Mark org.testng.Assert @deprecated (recommend AssertJ); add
  @SuppressWarnings("deprecation") on Assertion.
- pom: move assertj-core to compile scope (the main code depends on it).
- docs: add MIGRATING_ASSERTIONS.md and link it from the README.

expectThrows is kept unchanged (it returns the caught exception, which
AssertJ's fluent API does not expose).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Delegate assertTrue/assertFalse/fail to AssertJ

Follow-up to the AssertJ delegation: extend it to the remaining
behaviour-identical entry points.

- assertTrue/assertFalse -> assertThat(condition).as(message).isTrue()/isFalse()
- fail(message) and fail(message, cause) -> org.assertj.core.api.Assertions.fail(...)

These flow through the OO Assertion/SoftAssert wrappers automatically
(they delegate to org.testng.Assert), so soft assertions now use AssertJ
for these checks too. Only the failure-message text changes; the
pass/fail behaviour is unchanged.

Updated the message-format-sensitive tests accordingly:
- assertTrue/assertFalse message regexps now match AssertJ's output.
- SoftAssertTest#testAssertAllCount no longer counts report lines (AssertJ
  messages are multi-line); it asserts the failed message is reported once.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

architecture Internal architecture / refactoring Feature: assertions Assert / assertEquals and assertion utilities testng-api Public API changes / compatibility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants