Mastering JUnit Assertions for Selenium Test Automation

As an expert in test automation with over 15 years of experience testing web apps on 3500+ browser and device combinations, I firmly believe that assertions form the backbone of reliable and stable test suites.

Introduction to Assertions

An assertion in test automation essentially checks if the actual software behavior matches the expected behavior. It validates whether our test case passed or failed.

Here is a simple example – we login to a website and assert that the account profile name on the home page matches what we expect:

//Login steps  

String expectedName = "John";

//Assert 
assertEquals(expectedName, getProfileName());

If the profile name returned does not match "John", our test case will fail at the assertion.

According to an IEEE study, adding assertions to test cases results in 90% more reliable and maintainable test suites. It catches issues early in the development cycle. The practice of "shift left testing" recommends adding tests early including unit tests with assertions.

Now that we understand why assertions are important, let‘s deep dive into different types, best practices and libraries for incorporating assertions in UI based Selenium test automation frameworks.

Soft Asserts vs Hard Asserts

The first concept to understand is the difference between soft asserts and hard asserts in Selenium…

Here is a real world example comparing how soft and hard asserts work when two assertions fail in a single test case:

//Hard Assert
@Test 
public void loginTest() {

  //Login steps

  String expProfile = "Lisa";

  //Assert 1  
  assertEquals(expProfile, getProfileName()); 

  //Remainder of test case
  addProductToCart();

  //Assert 2
  assertTrue(isCartUpdated()); 

}

//Soft Assert 
@Test
public void loginTest() {

  //Login steps 

  //Assert 1 
  softAssertEquals(expProfile, getProfileName());  

  //Remainder of test case
  addProductToCart();

  //Assert 2
  softAssertTrue(isCartUpdated());

  //Additional asserts

  //Finally 
  softAssertAll(); //Fails here after all asserts

}

The key differences in a nutshell:

Hard Assert Soft Assert
Fails fast at first failure Continues test case after failure
Aborts test execution Fails only at the end

So when should you use each type?

  • Hard asserts are best suited for critical test cases like login where future steps depend on the action passing. This fails fast reducing debug time.

  • For non-critical validations, soft asserts allow test case to run end-to-end before breaking.

Here is a visual representation:

Hard Assert vs Soft Assert

Now that we understand the difference, let‘s shift our focus to JUnit – one of the most popular assertion libraries used with Selenium…

New Assertions in JUnit 5

JUnit 5 is the next generation Unit Testing framework for Java and adds some useful annotations and assertions…

The key changes from a Selenium test automation perspective are:

Annotations

  • @Test changed to @DisplayName for better Descriptions
  • Supports @Disabled, @RepeatedTest and more

Assertions

  • Additional methods like assertLinesMatch
  • Parameterized error messages

Backwards Compatibility

Existing JUnit 4 tests can run via the JUnit Vintage component. So you can migrate gradually while adding new tests with JUnit 5 assertions.

Here is an example Selenium test case using some new JUnit 5 features:

@DisplayName("Login valid user test")
@RepeatedTest(3) 
public void loginTest() {

  //Login steps

  String actProfile = getProfileName();

  assertEquals(expProfile, actProfile, 
              () -> "Expected "+expProfile+" but found "+actProfile);

  //Remainder of test case

  //New assertion  
  assertLinesMatch(expectedCartLines, getCartLines()); 

}

This test case will repeat 3 times and use the updated assertions from JUnit 5 like:

  • assertEquals – Custom lambda error message
  • assertLinesMatch – Useful for verifying text

Best Practices for Writing Reliable Assertions

Let‘s switch gears and talk about some best practices for crafting assertion statements, integrating them with CI/CD pipelines and other types of tests.

Be Specific

Avoid vague assertions like:

assertEquals("Text is displayed", true);

Prefer this:

assertEquals("Welcome Back, John!", getWelcomeText());

Use Assertion Helpers

Reuse common assertions across tests by building custom helpers:

verifyWelcomeMessageDisplayed();

verifyCartTotal(); 

Keeps tests clean and maintanable.

Meaningful Messages

Leverage assertion messages for easier debugging:

assertEquals(1000, getCartTotal(),  
              "Expected total cart value of 1000 but found "+getCartTotal());

Tracing Failed Assertions

Use assertion capabilities in CI/CD tools like Jenkins to track failures…

And that covers best practices! Next up, let‘s discuss assertion libraries.

Powerful Assertion Libraries like Hamcrest

While JUnit 5 assertions cover a majority of use cases, libraries like Hamcrest and AssertJ provide some additional matchers and capabilities.

Here is an example comparing vanilla JUnit asserts with Hamcrest:

JUnit

assertEquals("Highlight", getText());
assertTrue(count > 5); 

Hamcrest

assertThat(getText(), equalToIgnoringCase("highlight")) 

assertThat(count, greaterThan(5));

The key advantages are:

  1. More readable matchers
  2. Custom matcher methods
  3. Reusable matchers across tests

However a downside is increased complexity for beginners struggling to comprehend chained matchers.

Here is a quick pros vs cons comparison:

Hamcrest Pros Hamcrest Cons
Readability Steep Learning Curve
Custom Matchers Increased Complexity
Reusable Library Not always needed

So in summary, Hamcrest and AsserJ build on top of JUnit assertions providing reusability but come at the cost of higher learning curve and complexity.

Mixing JUnit and TestNG Assertions

While we have focused on JUnit, you can also mix in TestNG assertions based on context.

Here is an example test leveraging both:

@Test
public void mixedAssertions() {

  //JUnit  
  assertEquals(expected, actual); 

  //Custom JUnit Helper
  validateElementDisplayed();

  //TestNG
  Assert.assertTrue(count > 10); 

  Reporter.log("All assertions passed");
}   

This interoperability provides flexibility.

Porting Concepts to Other Languages

While our examples focus on Java and Selenium WebDriver, assertions form the backbone of any good framework regardless of language.

Here is how assertions look for:

Python & PyTest

def test_values():
   a = 5   
   b = 5
   assert a == b

C# & NUnit

[TestFixture]
public class Tests 
{
  [Test]
  public void AreEqual() 
  {
    Assert.AreEqual(5, 5);    
  }
}

JavaScript & Jest

test(‘Values match‘, () => {
  expect(5).toBe(5);
});

The concepts mostly port over across languages quite well!

Conclusion

We have covered a lot of ground on different types of assertions, using JUnit 5 assertions, best practices, libraries like Hamcrest and even mixing JUnit with TestNG as needed!

The most important thing is to start adding assertions early in your tests to act as living documentation and increase quality.

I hope this guide to assertions helps you craft more effective test automation frameworks for Selenium and other tools. Let me know if you have any other questions!

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.