Hero Background

Power Your Software Testing with AI Agents and Cloud

The Native AI-Agentic Cloud Platform to Supercharge Quality Engineering. Test Intelligently and Ship Faster.

Testing

TestNG Assertions: Hard vs Soft Asserts with Examples

TestNG assertions explained: Assert methods, SoftAssert with assertAll, failure messages, asserting exceptions, and a Selenium example of hard vs soft asserts.

Published on:

TestNG assertions are the checks that decide whether a test passes. A hard assertion from org.testng.Assert stops the test at the first failure; a soft assertion from org.testng.asserts.SoftAssert records every failure and reports them together. This chapter covers both, the messages that make failures readable, how to assert exceptions, and a Selenium example that mixes the two styles on purpose.

This chapter is part of the TestNG tutorial, which covers setup, annotations, testng.xml, data-driven tests, and parallel execution.

TL;DR

  • Assert.assertEquals(actual, expected, message) is the workhorse. Actual comes first, which is the opposite of JUnit.
  • Hard assertions throw immediately. Use them for preconditions: if the login failed, nothing after it is worth checking.
  • Soft assertions collect failures. Use them for a batch of independent checks on one screen, and always finish with assertAll().
  • Assert exceptions with expectedExceptions on @Test, or with Assert.assertThrows when you need the exception object.

What Are TestNG Assertions?

An assertion compares what the code produced with what you expected and throws an AssertionError when they differ. TestNG catches that error, marks the test as failed, and prints the message and the two values in the report. A test method with no assertion cannot fail on behaviour; it only fails on exceptions, which is why a test that "passes" without assertions proves nothing.

TestNG ships two assertion styles. The static methods on Assert are hard assertions: the first failure ends the method. SoftAssert is an object you create per test; its methods record failures instead of throwing, and assertAll() throws once with every recorded failure. Everything else, from the TestNG Annotations that run setup to the reports that show the result, is the same for both.

Hard Assertions with the Assert Class

The methods you will use most, all static on org.testng.Assert:

  • assertEquals(actual, expected) and assertNotEquals, with overloads for primitives, objects, arrays, collections, and maps. Floating-point overloads take a delta.
  • assertTrue(condition) and assertFalse(condition) for boolean results.
  • assertNull(object) and assertNotNull(object).
  • assertSame and assertNotSame, which compare references rather than equals().
  • assertThrows(Class, ThrowingRunnable) and expectThrows, which returns the exception.
  • fail(message), for a branch that must never execute.
import org.testng.Assert;
import org.testng.annotations.Test;

public class CartTest {

    @Test
    public void totalIncludesTax() {
        Cart cart = new Cart();
        cart.add("book", 10.00);

        Assert.assertEquals(cart.total(), 11.80, 0.001, "total with 18% tax");
        Assert.assertTrue(cart.items().size() == 1, "one line item expected");
        Assert.assertNotNull(cart.lastAddedAt(), "timestamp should be set on add");
    }
}

The first assertion that fails throws, so the second and third never run. That is the right behaviour when later checks depend on earlier ones, and the wrong behaviour when you want to see every problem on a page in a single run, which is what soft assertions are for.

Soft Assertions with SoftAssert

SoftAssert has the same method names as Assert, but each call records a failure and returns. Nothing is thrown until assertAll(), which throws one AssertionError listing every failure with its message. Create a new instance per test method; sharing one across tests mixes their failures.

import org.testng.annotations.Test;
import org.testng.asserts.SoftAssert;

public class ProfilePageTest {

    @Test
    public void profileShowsSavedDetails() {
        Profile profile = ProfileService.load("u-42");
        SoftAssert softly = new SoftAssert();

        softly.assertEquals(profile.name(), "Priya Nair", "name");
        softly.assertEquals(profile.email(), "priya@example.com", "email");
        softly.assertTrue(profile.isVerified(), "verified flag");
        softly.assertEquals(profile.roles().size(), 2, "role count");

        softly.assertAll();
    }
}

If three of the four checks fail, the report shows all three, each with its label, instead of only the first. The trap is forgetting assertAll(): without it the test passes no matter what was recorded. Many teams put the call in an @AfterMethod, or wrap SoftAssert in a small base class, so it cannot be left out.

Writing Assertion Messages That Help

Every assertion method takes an optional message as its last argument. TestNG appends the expected and actual values, so the message should say what was being checked, not repeat the values: "cart total after discount" beats "expected 11.80". In a suite of hundreds of tests, the message is often the only context a failure has, because the stack trace points at the assertion line rather than the cause.

Two habits keep messages useful. Name the business fact ("order status after payment") rather than the technical operation, and include the identifier of the thing under test when the data is dynamic ("status of order " + orderId). For collection assertions, prefer assertEquals on the whole list over a loop of single assertions, because the failure then shows both lists side by side.

Asserting Exceptions

Code that must throw can be asserted declaratively or programmatically. The declarative form goes on the annotation and is enough when the type is all you care about. The programmatic form gives you the exception object to inspect.

import org.testng.Assert;
import org.testng.annotations.Test;

public class WithdrawalTest {

    // declarative: the test passes only if this exception type is thrown
    @Test(expectedExceptions = InsufficientFundsException.class,
          expectedExceptionsMessageRegExp = ".*balance 50.*")
    public void rejectsOverdraft() {
        new Account(50).withdraw(80);
    }

    // programmatic: assertThrows returns nothing, expectThrows returns the exception
    @Test
    public void reportsTheShortfall() {
        InsufficientFundsException ex = Assert.expectThrows(
            InsufficientFundsException.class,
            () -> new Account(50).withdraw(80));

        Assert.assertEquals(ex.shortfall(), 30, "shortfall amount");
    }
}

The Exception Tests in TestNG chapter goes further into message matching and testing for exceptions that must not be thrown.

Assertions in a Selenium Test

Browser tests are where the hard-versus-soft choice matters most. The login must succeed before anything on the dashboard can be checked, so that assertion is hard. The dashboard checks are independent of each other, so they are soft, and one run reports every wrong label instead of the first.

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.testng.asserts.SoftAssert;

public class DashboardTest {
    private WebDriver driver;

    @BeforeMethod
    public void openBrowser() {
        driver = new ChromeDriver();
        driver.get("https://example.test/login");
    }

    @Test
    public void dashboardShowsAccountSummary() {
        driver.findElement(By.id("email")).sendKeys("priya@example.com");
        driver.findElement(By.id("password")).sendKeys("correct-horse");
        driver.findElement(By.id("sign-in")).click();

        // hard: nothing below makes sense if we are not on the dashboard
        Assert.assertTrue(driver.getCurrentUrl().endsWith("/dashboard"), "landed on dashboard");

        // soft: independent checks on the same screen
        SoftAssert softly = new SoftAssert();
        softly.assertEquals(driver.findElement(By.id("greeting")).getText(), "Welcome back, Priya", "greeting");
        softly.assertEquals(driver.findElement(By.id("plan")).getText(), "Team", "plan label");
        softly.assertTrue(driver.findElement(By.id("invoices")).isDisplayed(), "invoices panel visible");
        softly.assertAll();
    }

    @AfterMethod
    public void closeBrowser() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Hard or Soft: Which to Use

  • Use a hard assertion for anything later steps depend on: navigation succeeded, the element exists, the API returned 200.
  • Use soft assertions for a set of independent checks on one screen or one response body, where seeing all failures at once saves a re-run.
  • Do not mix them by accident. A hard assertion placed after soft ones will stop the method before assertAll(), hiding the soft failures.
  • Keep the count small. A test with thirty soft assertions is usually three tests. Split it, and the report tells you which behaviour broke.
  • Prefer assertions over logging. A System.out.println of the actual value never fails a build. If a value matters, assert on it.

Conclusion

TestNG gives you hard assertions for the checks that gate a test and soft assertions for the checks that should all be reported together. Name what each assertion verifies, put the actual value first, and finish every SoftAssert with assertAll(). The next chapter, Test Priority in TestNG, controls the order those tests run in.

Author

...

Devansh Bhardwaj

Blogs: 80

  • Twitter
  • Linkedin

Devansh Bhardwaj is a Community Evangelist at TestMu AI with 4+ years of experience in the tech industry. He has authored 30+ technical blogs on web development and automation testing and holds certifications in Automation Testing, KaneAI, Selenium, Appium, Playwright, and Cypress. Devansh has contributed to end-to-end testing of a major banking application, spanning UI, API, mobile, visual, and cross-browser testing, demonstrating hands-on expertise across modern testing workflows.

Reviewer

...

Harish Rajora

Reviewer

  • Linkedin

Harish Rajora is a Software Developer 2 at Oracle India with over 6 years of hands-on experience in Python and cross-platform application development across Windows, macOS, and Linux. He has authored 800 + technical articles published across reputed platforms. He has also worked on several large-scale projects, including GenAI applications, and contributed to core engineering teams responsible for designing and implementing features used by millions. Harish has worked extensively with Django, shell scripting, and has led DevOps initiatives, building CI/CD pipelines using Jenkins, AWS, GitLab, and GitHub. He has completed his post-graduation with an M.Tech in Software Engineering from the Indian Institute of Information Technology (IIIT) Allahabad. Over the years, he has emphasized the importance of planning, documentation, ER diagrams, and system design to write clean, scalable, and maintainable code beyond just implementation.

Add to Google preferred sources

Summarise with AI

Copied to Clipboard!
...

3000+ Browsers. One Platform.

See exactly how your site performs everywhere.

Try it free
...

Write Tests in Plain English with KaneAI

Create, debug, and evolve tests using natural language.

Try for free

TestNG Assertions FAQs

Did you find this page helpful?

More Related Learning Hubs

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests