Brief Library Descriptions
JUnit
We would start with a trendy testing framework. This article will focus more on JUnit 5 than JUnit 4 since it is becoming popular in programming. However, it’s worth mentioning that JUnit 4 is still widely used for all kinds of applications and programs.
Let's kick things off with a straightforward example of how to use JUnit for testing. In this case, we use the assertEquals method, which checks whether two variables are equal.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assertions;
public class JUnitSimpleTests {
@Test
public void simpleEqual() {
int result = 5 + 6;
Assertions.assertEquals(11, result);
}
}
Alternatively, we can enhance code readability by using static import for the JUnit's assertion methods:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class JUnitSimpleTests {
@Test
public void simpleEqual() {
int result = 5 + 6;
assertEquals(11, result);
}
}
We can also add a custom error message displayed when an assertion fails.
JUnit provides a range of other public methods available to be used with the followind assert keywords: assertAll(), assertArrayEquals(), assertTrue(), assertFalse(), assertNull(), assertNotNull(), assertNotEquals(), and many more. These assert statement are intuitive to use within an Integrated Development Environment (IDE). However, when viewed outside the IDE, developers must remember which argument represents the expected value and which represents the actual value.
Refer to the comprehensive documentation for a list of all available assertion check methods or potential question.
TestNG
Let's explore another testing framework similar to JUnit in terms of assertion syntax. TestNG provides a familiar way to assertions performance. In the following example, we'll see how the previous test case would look when using TestNG in your program:
import org.testng.annotations.Test;
import org.testng.Assert;
public class TestNgSimpleTests {
@Test
public void simpleEqual() {
int result = 5 + 6;
Assert.assertEquals(11, result);
}
}
You'll notice that the method structure remains similar to JUnit, but instead of the Assertions class, we use Assert keyword in TestNG. Additionally, you can opt for a static import and include a custom message for better clarity.
import org.testng.annotations.Test;
import static org.testng.Assert.*;
public class TestNgSimpleTests {
@Test
public void simpleEqual() {
int result = 5 + 6;
assertEquals(11, result, "Values should be equal");
}
}
TestNG also offers a variety of other assert method invocation like “assertNotEquals()”, “assertNull()”, “assertTrue()”, “assertFalse()”, “assertNotNull()”, and more.
Notably, TestNG supports soft and hard assertions, which we will discuss later.
For more details and help with TestNG, check out the documentation. Make sure you choose the right library version when visiting the documentation.
Hamcrest
While still popular, it may not be as widely used as it once was. What sets Hamcrest apart is its extensive collection of matchers, allowing for complex assertions by combining matchers with logical operators. You can use Hamcrest with both JUnit and TestNG for your testing needs.
However, it's worth noting that the Hamcrest community has become less active over time. The latest version of Hamcrest was released 2019 roughly three years ago. This suggests that Hamcrest may not be actively maintained, potentially resulting in compatibility issues with newer Java versions.
Let's take a look at a basic example of using Hamcrest with JUnit:
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
public class HamcrestSimpleTests {
@Test
public void simpleEqual() {
int result = 5 + 6;
assertThat(result, equalTo(11));
}
}
As you can see, Hamcrest introduces a different syntax with matcher conditions. We'll explore more examples of Hamcrest's capabilities later in the article.
For in-depth information and documentation on Hamcrest, you can visit their website.
AssertJ
Let’s move to the widely used library for fluent assertions, AssertJ. AssertJ stands out for its extensive range of assertions designed for various data types. It provides special modules tailored for different purposes, including Core, Guava, Joda Time, Neo4J, DB, and Swing modules.
What makes AssertJ particularly appealing is its clear and readable assertion syntax. It's easy to discern which value is expected and which one represents the actual result.
Let's examine a simple example of using AssertJ with JUnit:
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public class AssertJSimpleTests {
@Test
public void simpleEqual() {
int result = 5 + 6;
Assertions.assertThat(result).isEqualTo(11);
}
}
Similarly to the previous libraries, you can simplify the production code base with static import for AssertJ.
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class AssertJSimpleTests {
@Test
public void simpleEqual() {
int result = 5 + 6;
assertThat(result).isEqualTo(11);
}
}
For comprehensive documentation and further information on AssertJ, you can visit their website.
Truth
Let's explore another library known for its fluent assertions, Truth. Developed by Google's Guava team, Truth draws inspiration from AssertJ, resulting in a similar syntax. However, it's worth noting that Truth offers a more limited set of assertions than AssertJ.
Here's a straightforward example of using Truth with JUnit.
import org.junit.jupiter.api.Test;
import static com.google.common.truth.Truth.assertThat;
public class TruthSimpleTests {
@Test
public void simpleEqual() {
int result = 5 + 6;
assertThat(result).isEqualTo(11);
}
}
As you can see, the method structure in Truth looks similar to AssertJ.
For further details and documentation on Truth, visit their website.
Different Cases
This section will delve into various scenarios and explore how different assertion libraries can be applied effectively. We will examine cases ranging from comparing primitive types and handling collections to working with custom objects. Additionally, we will explore scenarios like multiplication, dealing with normal exceptions, defining timeouts, and creating custom assertions.
It's crucial to have a solid grasp of these cases and understand how to employ the right assertion library for each situation. While the library choice depends on your project's specific requirements and personal preferences, this section will equip you with the knowledge to make informed decisions.
Simple Comparison Assertions
Let's explore basic comparison assertions for various testing libraries. These assertions help you verify if values are equal or not. They apply to different data types, not only primitive but also more complex ones. These bare comparison assertions provide a foundation for more advanced testing scenarios. It's important to note that while these fundamental comparisons are helpful, they should not be overused. Consider using more specific methods available within these libraries to enhance code clarity.
JUnit/TestNG
JUnit and TestNG share a similar syntax for these comparisons. Here's how you can check for equality:
@Test
public void testSimpleEqual() {
int expectedYear = 2023;
int actualYear = 2023;
assertEquals(expectedYear, actualYear, "Values should be equal");
}
To check for inequality, use a different method with the same set of arguments:
@Test
public void simpleNotEqual() {
String unexpectedMethodName = "000_Sth Test";
String actualMethodName = "simpleNotEqual";
assertNotEquals(unexpectedMethodName, actualMethodName, "Values shouldn't be equal");
}
Hamcrest
Hamcrest has its unique approach. When comparing for equality, use the equalTo() method:
@Test
public void simpleEqual() {
int expectedYear = 2023;
int actualYear = 2023;
assertThat(actualYear, equalTo(expectedYear));
}
To check for inequality, use the not() matcher, which negates the equalTo() matcher:
@Test
public void simpleNotEqual() {
String unexpectedMethodName = "000_Sth Test";
String actualMethodName = "simpleNotEqual";
assertThat(actualMethodName, not(equalTo(unexpectedMethodName)));
}
AssertJ/Truth
AssertJ and Truth libraries offer similar syntax for these cases. When checking for equality, you can use the isEqualTo() method:
@Test
public void simpleEqual() {
int expectedYear = 2023;
int actualYear = 2023;
assertThat(actualYear).isEqualTo(expectedYear);
}
To verify inequality, use the isNotEqualTo() method:
@Test
public void simpleNotEqual() {
String unexpectedMethodName = "000_Sth Test";
String actualMethodName = "simpleNotEqual";
assertThat(actualMethodName).isNotEqualTo(unexpectedMethodName);
}
Boolean Assertions
This section'll focus on boolean assertions, a specific type of primitive value. When dealing with boolean conditions, libraries can offer dedicated methods which enhance code clarity and readability.
JUnit/ TestNG
For this case, JUnit and TesNg also have similar syntax.
To check whether a value is true, use assertTrue():
@Test
public void checkingTrue() {
boolean areFantasticTests = true;
assertTrue(areFantasticTests);
}
To check whether a value is false, use assertFalse():
@Test
public void checkingFalse() {
boolean isEverydaySaturday = false;
assertFalse(isEverydaySaturday);
}
Hamcrest
Hamcrest takes a slightly different approach, using the is() matcher for both checks.
You can use is(true) to check whether a value is true.
@Test
public void checkingTrue() {
boolean areFantasticTests = true;
assertThat(areFantasticTests, is(true));
}
You can use is(false) to check whether a value is false.
@Test
public void checkingFalse() {
boolean isEverydaySaturday = false;
assertThat(isEverydaySaturday, is(false));
}
AssertJ/Truth
AssertJ and Truth libraries provide dedicated isTrue() and isFalse() methods for boolean assertions.
Here is an example with isTrue() checking if a value is true.
@Test
public void checkingTrue() {
boolean areFantasticTests = true;
assertThat(areFantasticTests).isTrue();
}
With isFalse() we can check if a value is false.
@Test
public void checkingFalse() {
boolean isEverydaySaturday = false;
assertThat(isEverydaySaturday).isFalse();
}
Number Assertions
In this subsection, we’ll look at the number assertions. It's worth noting that not all of these libraries have dedicated methods for number assertions. The assertions we discuss apply to a range of numeric types, including integers (int), floating-point numbers (double and float), and long integers (long). Some also extend their support to the BigDecimal data type. Our examples will primarily concentrate on integers for simplicity.
JUnit/TestNG
Neither JUnit nor TestNG offers dedicated methods for number assertions. Instead, you have to rely on more general assertion methods. For instance, you can utilize assertTrue() to determine whether a value is greater than another.
@Test
public void checkingGreater() {
int donutsToEat = 5;
assertTrue(donutsToEat > 3);
}
While it's possible to write unit tests using these general methods, using dedicated number assertion methods often provides greater convenience.
Hamcrest
Hamcrest, on the other hand, provides dedicated matchers for number assertions.
You can use these matchers to check if a number is greater or less than an expected value.
assertThat(6, greaterThan(5)); // pass
assertThat(5, greaterThan(5)); // fail
assertThat(6, greaterThanOrEqualTo(5)); // pass
assertThat(5, greaterThanOrEqualTo(5)); // pass
assertThat(4, lessThan(5)); // pass
assertThat(5, lessThan(5)); // fail
assertThat(4, lessThanOrEqualTo(5)); // pass
assertThat(5, lessThanOrEqualTo(5)); // pass
Moreover, you can use Hamcrest to check if a number is approximately equal to an expected value, a feature that works for double and BigDecimal data types.
// checking if number is between 4.7 and 5.3 (inclusive)
assertThat(4.8, closeTo(5.0, 0.3)); // pass
assertThat(4.6, closeTo(5.0, 0.3)); // fail
// checking if number is between 9.5 and 10.5 (inclusive)
assertThat(new BigDecimal("10.5"),
closeTo(BigDecimal.TEN, new BigDecimal("0.5"))); // pass
assertThat(new BigDecimal("10.6"),
closeTo(BigDecimal.TEN, new BigDecimal("0.5"))); // fail
AssertJ
AssertJ stands out with its extensive range of dedicated methods for number assertions. This library provides various methods to assert different aspects of numbers.
// check if value is 0 (or 0.0)
assertThat(0).isZero();
// check if value is not 0 (or not 0.0)
assertThat(1).isNotZero();
// check if value is greater than 0
assertThat(8).isPositive();
// check if value is less than 0
assertThat(-7.06).isNegative();
// check if value is even
assertThat(22).isEven();
// check if value is odd
assertThat(5).isOdd();
// check if value is less than the second value
assertThat(-8).isLessThan(10);
// check if value is less or equal to the second value
assertThat(5).isLessThanOrEqualTo(5);
// check if value is greater than the second value
assertThat(20).isGreaterThan(19);
// check if value is greater or equal to the second value
assertThat(100).isGreaterThanOrEqualTo(80);
// check if value is withing given range (inclusive)
assertThat(-5).isBetween(-5, 3);
// check if value is withing given range (inclusive)
assertThat(4).isStrictlyBetween(-1, 7);
Truth
Truth offers dedicated methods for number assertions. These methods cover various scenarios, and not all are implemented for all number types, such as integers:
// check if value is min expected value
assertThat(11).isAtLeast(10);
// check if value is max expected value
assertThat(9).isAtMost(10);
// check if value is less than the second value
assertThat(9).isGreaterThan(5);
// check if value is greater than the second value
assertThat(9).isLessThan(20);
Now, look at the examples of doubles.
// check if value is 0.0
assertThat(0.0f).isZero();
// check if value is not 0.0
assertThat(1.0f).isNonZero();
// check if value is not infinite
assertThat(11.5).isFinite(); // pass
assertThat(Double.POSITIVE_INFINITY).isFinite(); // fail
// check if value is positive infinity
assertThat(Double.POSITIVE_INFINITY).isPositiveInfinity();
// check if value is negative infinity
assertThat(Double.NEGATIVE_INFINITY).isNegativeInfinity();
// check if value is not NaN
assertThat(1.0f).isNotNaN();
// check if value is NaN
assertThat(Double.NaN).isNaN();
// check if value is not in range
assertThat(2.55).isNotIn(Range.closed(1.0, 2.5));
// check if value is not in range
assertThat(4.5).isIn(Range.closedOpen(3.34, 4.5));
To explore more, you can refer to Javadoc or the library's source code:
Similar source code is available for Float (FloatSubject) and BigDecimal (BigDecimalSubject) assertions.
This revised section provides a clearer and more refined explanation of number assertions in Java, focusing on each library's unique features.
String Assertions
Let's focus on string assertions. Similar to number assertions, only some libraries have dedicated methods. However, libraries can contain various dedicated methods, and showcasing some of them is only possible.
JUnit/TestNG
We have the same case as for number assertions. You must use general assertions, as there are no dedicated string assertions for JUnit or TestNG. For example, assertFalse() can check if a string is not blank.
@Test
public void checkingBlank() {
var spell = "Expecto Patronum";
assertFalse(spell.isBlank());
}
Hamcrest
Hamcrest provides numerous matchers for string assertions. Here are some examples:
// check if your string is empty
assertThat("", is(emptyString()));
// check if your string is equal to expected ignoring cases
assertThat("Expelliarmus", equalToIgnoringCase("expelliarmus"));
// check if your string is equal to expected ignoring spaces
assertThat("Expecto Patronum ", equalToCompressingWhiteSpace("Expecto Patronum "));
//check if your string contains the given substring (cases are important / ignored cases)
assertThat("Wingardium Leviosa", containsString("Lev"));
assertThat("Wingardium Leviosa", containsStringIgnoringCase("leV"));
//check if your string starts with the given substring (cases are important / ignored cases)
assertThat("Wingardium Leviosa", startsWith("Win"));
assertThat("Wingardium Leviosa", startsWithIgnoringCase("wIn"));
//check if your string ends with the given substring (cases are important / ignored cases)
assertThat("Wingardium Leviosa", endsWith("osa"));
assertThat("Wingardium Leviosa", endsWithIgnoringCase("OSA"));
AssertJ
AssertJ also has many dedicated assert methods related to string. There are so many methods, so we will group them and mention only a few. You can look in Javadoc for more.
First, let’s start by checking whether a string is empty or blank.
assertThat(" ").isBlank();
assertThat(" ").isNotEmpty();
We can also check the string’s size.
assertThat("Lumos").hasSameSizeAs("Accio");
assertThat("Lumos").hasSizeBetween(2, 7);
We also have methods checking if your string contains specific values. You can look at the whole string or only the beginning or ending.
assertThat("Wingardium Leviosa").containsIgnoringCase("GarD");
assertThat("Wingardium Leviosa").containsWhitespaces();
assertThat("Wingardium Leviosa").startsWith("Win");
You can also check if your sting doesn’t contain specific values.
assertThat("Expelliarmus").doesNotContain("liars");
assertThat("Expelliarmus").doesNotContainAnyWhitespaces();
assertThat("Expelliarmus").doesNotEndWith("MUS");
You can also check patterns and regex. Some methods have defined regex, including checking lowercase or mixed.
assertThat("Expecto Patronum").containsPattern("to.*ron");
assertThat("Expecto Patronum").doesNotMatch(Pattern.compile("pec.num"));
//defined regex
assertThat("WingardiumLeviosa").isAlphabetic();
assertThat("Wingardium0Leviosa").isAlphabetic(); //fail
assertThat("Lumos0").isAlphanumeric();
assertThat("Lumos!").isAlphanumeric(); //fail
assertThat("alohomora").isLowerCase();
assertThat("Alohomora").isMixedCase();
Truth
Truth also has string asserts, but only so many as in AssertJ.
//check if empty
assertThat(" ").isNotEmpty();
assertThat("").isEmpty();
//check size
assertThat("Lumos").hasLength(5);
// check if contains / not contains
assertThat("Wingardium Leviosa").contains("Lev");
assertThat("Wingardium Leviosa").startsWith("Win");
assertThat("Wingardium Leviosa").endsWith("osa");
assertThat("Expelliarmus").doesNotContain("liars");
// check regex and pattern
assertThat("Expecto Patronum").containsMatch("to.*ron");
assertThat("Expecto Patronum").containsMatch(Pattern.compile("pec[p-u]"));
assertThat("Expecto Patronum").doesNotContainMatch("ec..o");
assertThat("Expecto Patronum").matches(".*ec.o.*");
Collection and Arrays Assertions
We'll categorize collections into distinct groups for clarity. The Iterable interface extends to List, Queue, and Set, while the Map interface stands independently. Though not directly related to the mentioned groups, we'll also consider arrays.
You can look at the diagram to see the example class. There are not all of them, only the most used ones.
Test libraries may feature methods exclusive to collections implementing the Iterable interface. However, some methods can validate both Iterable and Map objects. The article provides a few examples of usage; refer to the Javadoc. Switch the map to a set (using the method entrySet()) if you prefer assertions dedicated only to Iterable objects.
JUnit
JUnit offers specific methods for comparing arrays and iterable objects. For arrays, you can use assertArrayEquals(). For Iterable, we have assertIterableEquals().
var encantoArray =
{
"Mirabel", "Abuela", "Isabela", "Abuelo", "Alma",
"Antonio", "Dolores", "Julieta", "Felix", "Agustín"
};
var copyEncantoArray = Arrays
.copyOf(encantoArray, encantoArray.length);
// compare two arrays
assertArrayEquals(encantoArray, copyEncantoArray);
var housesList =
List.of(
"Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin"
);
var copyHousesList = new ArrayList<>(housesList);
// compare two Iterable objects
assertIterableEquals(housesList, copyHousesList);
TestNG
TestNG provides dedicated assertions for collections and arrays.
var doctorsMap = Map.of(
"Ninth Doctor", "Christopher Eccleston",
"Tenth Doctor", "David Tennant",
"Meta-Crisis Doctor", "David Tennant",
"Eleventh Doctor", "Matt Smith",
"War Doctor", "John Hurt",
"Twelfth Doctor", "Peter Capaldi",
"Thirteenth Doctor", "Jodie Whittaker",
"Fugitive Doctor", "Jo Martin",
"Fourteenth Doctor", "David Tennant",
"Fifteenth Doctor", "Ncuti Gatwa"
);
//compare nested map or set
assertEqualsDeep(doctorsMap, copyDoctorsMap);
// available also assertNotEqualsDeep();
var housesList =
List.of(
"Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin"
);
var copyHousesList = new ArrayList<>(housesList);
var housesListShuffled =
List.of(
"Hufflepuff", "Slytherin", "Gryffindor", "Ravenclaw"
);
//check if two arrays/collections have the same elements in no particular order
assertEqualsNoOrder(housesListShuffled, housesList);
//check if a list contains a specific object
assertListContains(housesList, h -> h.contains("ave"), "not found object");
//Also available: assertListNotContains()
//check if a list contains a specific object
assertListContainsObject(housesList, "Ravenclaw", "not found object");
//Also available: assertListNotContainsObject()
Hamcrest
Hamcrest offers numerous matchers related to arrays and collections. Refer to the javadoc for details.
Let’s start with arrays.
//check if array is empty
var emptyArray = new String[]{};
assertThat(emptyArray, emptyArray());
var smallArray = new String[]{"bed", "sad", "cat"};
//check the array size
assertThat(smallArray, arrayWithSize(3));
assertThat(smallArray, arrayWithSize(greaterThan(2)));
// check if an array has specific elements and only them
//order is important
assertThat(smallArray, arrayContaining("bed", "sad", "cat"));
// check if array's elements satisfy matchers
// all items should have corresponding matcher
// order is important
assertThat(smallArray, arrayContaining(
List.of(startsWith("b"), startsWith("s"), endsWith("t")))
);
assertThat(smallArray, arrayContaining(
startsWith("b"), startsWith("s"), endsWith("t"))
);
// check if an array has specific elements and only them
//order is not important
assertThat(smallArray, arrayContainingInAnyOrder("sad", "cat", "bed"));
// check if array's elements satisfy matchers
// all items should have corresponding matcher
// order is important
assertThat(smallArray, arrayContainingInAnyOrder(
List.of(startsWith("s"), endsWith("t"), startsWith("b")))
);
assertThat(smallArray, arrayContainingInAnyOrder(
startsWith("b"), endsWith("t"), startsWith("s"))
);
// check if an array contains specific element
assertThat(smallArray, hasItemInArray("cat"));
// check if at least one array's element satisfies a matcher
assertThat(smallArray, hasItemInArray(startsWith("c")));
Let’s now check the matchers for Collection.
Set emptySet = Set.of();
//check if the collection is empty
assertThat(emptySet, empty());
//check if the collection is empty and checked type of possible elements
assertThat(emptySet, emptyCollectionOf(String.class));
var housesList = List.of(
"Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin"
);
// check collection's size
assertThat(housesList, hasSize(4));
assertThat(housesList, hasSize(lessThan(10)));
We have also the matchers dedicated for Iterables.
List emptyList = List.of();
//check if Iterable is empty
assertThat(emptyList, emptyIterable());
//check if Iterable is empty and checked type of possible elements
assertThat(emptyList, emptyIterableOf(Integer.class));
var housesList = List.of(
"Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin"
);
// check Iterable's size
assertThat(housesList, iterableWithSize(4));
assertThat(housesList, iterableWithSize(lessThan(10)));
// check if Iterable has specific elements and only them
//order is important
assertThat(housesList,
contains("Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin")
);
// check if Iterable's elements satisfy matchers
// all items should have corresponding matcher
// order is important
assertThat(housesList, contains(
List.of(startsWith("G"), startsWith("H"), endsWith("w"), endsWith("n")))
);
assertThat(housesList, contains(
startsWith("G"), startsWith("H"), endsWith("w"), endsWith("n"))
);
// check if Iterable has specific elements and only them
//order is not important
assertThat(housesList,
containsInAnyOrder("Ravenclaw", "Hufflepuff", "Gryffindor", "Slytherin")
);
// check if Iterable's elements satisfy matchers
// all items should have corresponding matcher
// order is not important
assertThat(housesList, containsInAnyOrder(
List.of(endsWith("w"), endsWith("n"), startsWith("G"), startsWith("H")))
);
assertThat(housesList, containsInAnyOrder(
endsWith("w"), endsWith("n"), startsWith("G"), startsWith("H"))
);
// check if Iterable has specific elements and can contain more
//order is important - should be relative
assertThat(housesList,
containsInRelativeOrder("Gryffindor", "Ravenclaw")
);
// check if Iterable's elements satisfy matchers
// all matchers should have corresponding item
// order is important - should be relative
assertThat(housesList, containsInRelativeOrder(
List.of(startsWith("G"), endsWith("w")))
);
assertThat(housesList, containsInRelativeOrder(
startsWith("G"), endsWith("w"))
);
var companionsQueue = new ArrayDeque<>(
List.of("Rose Tyler", "Martha Jones", "Donna Noble")
);
// check if every Iterable's elements satisfy a matcher
assertThat(companionsQueue, everyItem(containsString("e")));
// check if Iterable contains specific element
assertThat(companionsQueue, hasItem("Donna Noble"));
// check if at least one Iterable's element satisfy a matcher
assertThat(companionsQueue, hasItem(startsWith("Rose")));
// check if Iterable has specific elements and can contain more
//order is not important
assertThat(companionsQueue, hasItems("Martha Jones", "Donna Noble"));
// check if Iterable's elements satisfy matchers
// all items should have corresponding matcher
// order is not important
assertThat(companionsQueue, hasItems(
startsWith("Rose"), endsWith("ble"))
);
Let’s focus also on dedicated matchers for maps.
var emptyMap = Map.of();
//check if map is empty
assertThat(emptyMap, anEmptyMap());
var doctorsMap = Map.of(
"Ninth Doctor", "Christopher Eccleston",
"Tenth Doctor", "David Tennant",
"Meta-Crisis Doctor", "David Tennant",
"Eleventh Doctor", "Matt Smith",
"War Doctor", "John Hurt",
"Twelfth Doctor", "Peter Capaldi",
"Thirteenth Doctor", "Jodie Whittaker",
"Fugitive Doctor", "Jo Martin",
"Fourteenth Doctor", "David Tennant",
"Fifteenth Doctor", "Ncuti Gatwa"
);
// check map's size
assertThat(doctorsMap, aMapWithSize(10));
assertThat(doctorsMap, aMapWithSize(greaterThanOrEqualTo(10)));
// check if map contains a specific entry
assertThat(doctorsMap, hasEntry("Tenth Doctor", "David Tennant"));
// check if map contains an entry which satisfy matchers for key and value
assertThat(doctorsMap,
hasEntry(startsWith("War"), containsStringIgnoringCase("hurt"))
);
// check if map contains a specific key
assertThat(doctorsMap, hasKey("Tenth Doctor"));
// check if map contains a key which satisfy a matcher
assertThat(doctorsMap, hasKey(startsWith("Ten")));
// check if map contains a specific value
assertThat(doctorsMap, hasValue("Matt Smith"));
// check if map contains a value which satisfy a matcher
assertThat(doctorsMap, hasValue(startsWith("Ma")));
AssertJ
AssertJ has a lot of dedicated assertions for arrays and collections. The following examples demonstrate some of them; look at the javadoc for more details.
First, look at the assertions for Iterable or arrays. You can find documentation on https://javadoc.io/static/org.assertj/assertj-core/3.24.2/org/assertj/core/api/AbstractIterableAssert.html and https://javadoc.io/static/org.assertj/assertj-core/3.24.2/org/assertj/core/api/AbstractObjectArrayAssert.html.
We would like to use arrays and list assertions in the examples below.
var housesList = List.of(
"Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin"
);
var smallArray = new String[]{"bad", "sad", "cat"};
var listWithNulls = Arrays.asList("sth", null, "nth");
var arrayWithNulls = new String[]{null, "sad", "cat"};
We have assertions related to sizes. We can not only check if size equals with expected value but also check if size is in the range or is greater or less than expected values.
// check if an array/ Iterable has specified size
assertThat(smallArray).hasSize(3);
assertThat(housesList).hasSize(4);
// check if an array has a size greater than specific number
assertThat(smallArray).hasSizeGreaterThan(2);
// check if Iterable has a size less or equal than specific number
assertThat(housesList).hasSizeLessThanOrEqualTo(4);
We also have assertions that check whether elements match predicates.
// check if an array/Iterable has only elements which satisfy a predicate
assertThat(smallArray).allMatch(e -> e.contains("a"));
assertThat(housesList).allMatch(e -> e.length() >= 9);
// check if an array/Iterable has at least one element which satisfy a predicate
assertThat(smallArray).anyMatch(e -> e.endsWith("d"));
assertThat(housesList).anyMatch(e -> e.startsWith("R"));
AssertJ has also many assertions which check if an array/iterable contains certain values
// check if an array/ Iterable contains specified elements in any order
assertThat(smallArray).contains("cat", "sad");
assertThat(housesList).contains("Ravenclaw", "Hufflepuff");
// check if an array/ Iterable contains at least one null
assertThat(arrayWithNulls).containsNull();
assertThat(listWithNulls).containsNull();
// check if an array/ Iterable contains only once specific elements
assertThat(smallArray).containsOnlyOnce("cat");
assertThat(housesList).containsOnlyOnce("Ravenclaw");
// check if an array/ Iterable doesn't contain specified elements
assertThat(smallArray).doesNotContain("dog");
assertThat(housesList).doesNotContain("Hogwart");
// check if an array/ Iterable doesn't contain nulls
assertThat(smallArray).doesNotContainNull();
assertThat(housesList).doesNotContainNull();
We can also check if elements satisfy some conditions, for example, if they are duplicates.
// check if an array/ Iterable has elements of one type
assertThat(smallArray).hasOnlyElementsOfType(String.class);
assertThat(housesList).hasOnlyElementsOfType(String.class);
// check if an array/ Iterable has no duplicates
assertThat(smallArray).doesNotHaveDuplicates();
assertThat(housesList).doesNotHaveDuplicates();
// check if an array/ Iterable has at least n elements which satisfy condition
Condition endsWithD =
new Condition<>(e -> e.endsWith("d"), "ends with d");
assertThat(smallArray).haveAtLeast(2, endsWithD);
Condition containsE =
new Condition<>(e -> e.contains("e"), "contains e");
assertThat(housesList).haveAtLeast(2, containsE);
We can also check if an array/iterable is sorted
// check if an array/ Iterable is sorted
var smallArraySorted = new String[]{"bad", "cat", "sad"};
assertThat(housesList).isSorted();
assertThat(smallArraySorted).isSorted();
AssertJ has also dedicated assertions for maps. You can find documentation here.
First, let’s start with simple assertions. We can check if the map is empty or null.
var smallMap = Map.of("key", "value");
var emptyMap = Map.of();
Map nullMap = null;
// check if map is null
assertThat(nullMap).isNull();
//check if map is not null
assertThat(emptyMap).isNotNull();
// check if map is empty
assertThat(emptyMap).isEmpty();
// check if map is not empty
assertThat(smallMap).isNotEmpty();
// check if map is null or empty
assertThat(nullMap).isNullOrEmpty();
assertThat(emptyMap).isNullOrEmpty();
We have assertions checking the map’s size. You can check also if the size is in range or is greater or less than expected values.
var heroesMovieDebut = Map.of(
"Iron Man", 2008,
"Thor", 2011,
"Captain America", 2011,
"Hulk", 2003,
"Black Widow", 2010
);
// check if map's size is as expected
assertThat(heroesMovieDebut).hasSize(5);
// check if map's size is greater than expected
assertThat(heroesMovieDebut).hasSizeGreaterThan(3);
//check if map's size is in range
assertThat(heroesMovieDebut).hasSizeBetween(2, 5);
We can also check whether a map contains expected entries, keys, or values. We can also extract value or entry.
var doctorsMap = java.util.Map.of(
"Ninth Doctor", "Christopher Eccleston",
"Tenth Doctor", "David Tennant",
"Meta-Crisis Doctor", "David Tennant",
"Eleventh Doctor", "Matt Smith",
"War Doctor", "John Hurt",
"Twelfth Doctor", "Peter Capaldi",
"Thirteenth Doctor", "Jodie Whittaker",
"Fugitive Doctor", "Jo Martin",
"Fourteenth Doctor", "David Tennant",
"Fifteenth Doctor", "Ncuti Gatwa"
);
// check if a map contains specific entry
assertThat(doctorsMap).containsEntry("Eleventh Doctor", "Matt Smith");
// check if a map contains specific key/ keys
assertThat(doctorsMap).containsKey("War Doctor");
assertThat(doctorsMap).containsKeys("War Doctor", "Meta-Crisis Doctor");
// check if a map contains specific value/ values
assertThat(doctorsMap).containsValue("David Tennant");
assertThat(doctorsMap).containsValues("David Tennant", "Matt Smith");
// check if a map doesn't contain specific entry
assertThat(doctorsMap).doesNotContainEntry("Eleventh Doctor", "John Hurt");
// check if a map doesn't contain specific key/ keys
assertThat(doctorsMap).doesNotContainKey("war doctor");
assertThat(doctorsMap).doesNotContainKeys("Time Doctor", "Mystery Doctor");
// check if a map doesn't contain specific value
assertThat(doctorsMap).doesNotContainValue("John Smith");
// extract a value of a specific key
assertThat(doctorsMap).extractingByKey("Tenth Doctor").isEqualTo("David Tennant");
Truth
Truth offers dedicated assertions for arrays, iterable objects, and maps.
Let's start with specialized assertions concerning arrays. It's worth noting that the available assertions can vary based on the element types. For instance, you can convert an int array to a list subject, but this is impossible for a double array.
var smallDoubleArray = new double[] {2.4, 1.1, 3.14};
var smallIntArray = new int[] {2,3,6};
var emptyArray = new char[] {};
// check if array is empty
assertThat(emptyArray).isEmpty();
// check if array is not empty
assertThat(smallDoubleArray).isNotEmpty();
assertThat(smallIntArray).isNotEmpty();
// check array's length
assertThat(smallDoubleArray).hasLength(3);
assertThat(smallIntArray).hasLength(3);
// change to list
// not implemented for all types of data, for example double
assertThat(smallIntArray).asList().contains(2);
There are a lot of dedicated assertions related to iterable objects.
var housesList = List.of(
"Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin"
);
var emptyList = List.of();
// check if Iterable is empty
assertThat(emptyList).isEmpty();
// check if Iterable is not empty
assertThat(housesList).isNotEmpty();
// check Iterable's size
assertThat(housesList).hasSize(4);
// check if Iterable contains specified object
assertThat(housesList).contains( "Hufflepuff");
// check if Iterable contains at least one of specified objects
assertThat(housesList).containsAnyOf("Snape","Slytherin", "Hogwart");
//check if Iterable contains all specified objects
// can contain other objects
assertThat(housesList).containsAtLeast("Gryffindor","Slytherin");
// check if Iterable doesn't contain any of specified objects
assertThat(housesList).containsNoneOf("Snape","Lumos", "Hogwart");
//check if Iterable doesn't contain specified object
assertThat(housesList).doesNotContain("Snape");
// check if Iterable doesn't contain duplicates
assertThat(housesList).containsNoDuplicates();
// check if Iterable is ordered
assertThat(housesList).isInOrder();
Truth has also dedicated assertions related to the maps.
var heroesMovieDebut = Map.of(
"Iron Man", 2008,
"Thor", 2011,
"Captain America", 2011,
"Hulk", 2003,
"Black Widow", 2010
);
var emptyMap = Map.of();
// check if the map is empty
assertThat(heroesMovieDebut).isNotEmpty();
// check if map is not empty
assertThat(emptyMap).isEmpty();
// check map's size
assertThat(heroesMovieDebut).hasSize(5);
// check if the map contains specified entries, but not only
assertThat(heroesMovieDebut)
.containsAtLeast("Captain America", 2011, "Hulk", 2003);
// check if the map contains specified entry
assertThat(heroesMovieDebut).containsEntry("Thor", 2011);
// check if map doesn't contain specified entry
assertThat(heroesMovieDebut).doesNotContainEntry("Thor", 2001);
// check if the map contains specified key
assertThat(heroesMovieDebut).containsKey("Thor");
// check if map doesn't contain specified key
assertThat(heroesMovieDebut).doesNotContainKey("thor");
Custom Object/Reference Type Assertions
When dealing with custom objects and reference types, it's essential to understand assertions tailored to these types. One approach is to override the compareTo() method, impacting general assertions.
For our examples, we'll use the CharacterDoctorWho record:
import java.util.List;
public record CharacterDoctorWho(String firstName, String lastName, List series) { }
Creating an instance is straightforward:
var exampleCharacter = new CharacterDoctorWho("Clara", "Oswald", List.of(7, 8, 9));
We'll also use the Cat class with the Lombok annotations for convenient getters, setters, a builder and a constructor:
import lombok.*;
@Data
@Builder
@AllArgsConstructor
public class Cat {
private String name;
private String breedCat;
private String colourFur;
private String patternFur;
private boolean isDomestic;
}
Initialization is also simple:
Cat garfield = Cat.builder()
.name("Garfield")
.breedCat("Persian")
.colourFur("Orange")
.isDomestic(true)
.build();
JUnit/TestNG
JUnit and TestNG provide two dedicated assertions related to objects, allowing you to verify whether an object is null or not:
@Test
public void checkObject() {
var actualCharacter= new CharacterDoctorWho("Amy", "Pond", List.of(5, 6, 7));
CharacterDoctorWho nullCharacter = null;
assertNull(nullCharacter);
assertNotNull(actualCharacter);
}
Hamcrest
Hamcrest offers various methods for object-related assertions. Let's explore some basic ones:
@Test
public void checkObjectsA() {
var amyA = new CharacterDoctorWho("Amy", "Pond", List.of(5, 6, 7));
var copyAmyA = amyA;
var amyB = new CharacterDoctorWho("Amy", "Pond", List.of(5, 6, 7));
CharacterDoctorWho nullCharacter = null;
//check if the object is null
assertThat(nullCharacter, is(nullValue()));
//check if the object is not null
assertThat(amyA, is(notNullValue()));
// check if objects are the same instances
assertThat(amyA, sameInstance(copyAmyA));
assertThat(amyA, not(sameInstance(amyB)));
// check if the object is an instance of CharacterDoctorWho
assertThat(amyA, instanceOf(CharacterDoctorWho.class));
}
Additionally, Hamcrest allows comparing objects while ignoring specified fields:
@Test
public void checkObjectsIgnoring() {
Cat cheshireCat = Cat.builder()
.name("Cheshire Cat").patternFur("Striped").isDomestic(false)
.build();
Cat foundCat = Cat.builder()
.patternFur("Striped").isDomestic(false)
.build();
// check objects, but ignore specified fields—"name"
assertThat(foundCat, samePropertyValuesAs(cheshireCat, "name"));
}
You can also examine if an object contains a specific field and check its value:
@Test
public void checkObjectsProperties() {
Cat goose = Cat.builder()
.name("Goose").breedCat("Flerken")
.colourFur("Brown/Orange").patternFur("Solid").isDomestic(false)
.build();
// check if object has a "name" field or property
assertThat(goose, hasProperty("name"));
// check if object's patterFur is "Solid"
assertThat(goose, hasProperty("patternFur", equalTo("Solid")));
}
AssertJ
AssertJ provides numerous dedicated assertions for objects.
Let's start with some simple assertions:
@Test
public void checkObjects() {
CharacterDoctorWho nullCharacter = null;
var amyA = new CharacterDoctorWho("Amy", "Pond",
List.of(5, 6, 7));
var copyAmyA = amyA;
var amyB = new CharacterDoctorWho("Amy", "Pond",
List.of(5, 6, 7));
// check if object is null/ not null
assertThat(nullCharacter).isNull();
assertThat(amyA).isNotNull();
// check if objects are the same instances
assertThat(amyA).isSameAs(copyAmyA);
assertThat(amyA).isNotSameAs(amyB);
// check if object is an instance of CharacterDoctorWho
assertThat(amyA).isInstanceOf(CharacterDoctorWho.class);
}
AssertJ also provides methods to ignore specific fields during object comparison:
@Test
public void checkObjectsIgnoring() {
Cat cheshireCat = Cat.builder()
.name("Cheshire Cat").patternFur("Striped").isDomestic(false)
.build();
Cat domesticCheshireCat = Cat.builder()
.name("Cheshire Cat").patternFur("Striped").isDomestic(true)
.build();
Cat foundCat = Cat.builder()
.patternFur("Striped").isDomestic(false)
.build();
// compare objects ignoring null fields
assertThat(foundCat).usingRecursiveComparison()
.ignoringActualNullFields()
.isEqualTo(cheshireCat);
// compare objects ignoring specified fields - "name"
assertThat(foundCat).usingRecursiveComparison()
.ignoringFields("name")
.isEqualTo(cheshireCat);
// compare objects ignoring specified fields - which names' match regex "na.*"
assertThat(foundCat).usingRecursiveComparison()
.ignoringFieldsMatchingRegexes("na.*")
.isEqualTo(cheshireCat);
// compare objects ignoring the specified type of fields - Boolean
assertThat(cheshireCat).usingRecursiveComparison()
.ignoringFieldsOfTypes(Boolean.class)
.isEqualTo(domesticCheshireCat);
}
You can also check fields or properties. You can make sure that they are all null or that none of them is null. You can check if there is no null field or property. There is also a method for extracting a specific field or checking if a specific field exists.
@Test
public void checkObjectsProperties() {
Cat goose = Cat.builder()
.name("Goose").breedCat("Flerken")
.colourFur("Brown/Orange").patternFur("Solid").isDomestic(false)
.build();
// check if object has no null fields or properties
assertThat(goose).hasNoNullFieldsOrProperties();
// check if the object has a "name" field or property
assertThat(goose).hasFieldOrProperty("name");
// check if object's patterFur is "Solid"
assertThat(goose).extracting("patternFur").isEqualTo("Solid");
Cat notKnownCat = Cat.builder().build();
// check if object has only null fields or properties, except isDomestic
assertThat(notKnownCat)
.hasAllNullFieldsOrPropertiesExcept("isDomestic");
}
Truth
Truth offers a few dedicated assertions for objects. You can check if an object is null or not null. These assertions include checking if an object is null or not null, if objects are the same instance, or if an object is an instance of a specific class.
@Test
public void checkObjectsA() {
CharacterDoctorWho nullCharacter = null;
var amyA = new CharacterDoctorWho("Amy", "Pond", List.of(5, 6, 7));
var copyAmyA = amyA;
var amyB = new CharacterDoctorWho("Amy", "Pond", List.of(5, 6, 7));
// check if an object is null/ not null
assertThat(nullCharacter).isNull();
assertThat(amyA).isNotNull();
// check if objects are the same instances
assertThat(amyA).isSameInstanceAs(copyAmyA);
assertThat(amyA).isNotSameInstanceAs(amyB);
// check if an object is an instance of CharacterDoctorWho
assertThat(amyA).isInstanceOf(CharacterDoctorWho.class);
}
Exceptions Assertions
JUnit and TestNG stand out among test libraries by offering dedicated assertions tailored for exception handling. In contrast, other test libraries lack specific assertions designed explicitly for dealing with exceptions.
JUnit/TestNG
Both JUnit 5 and TestNG provide an assertion called assertThrows() to verify the thrown exceptions. This assertion is valuable for ensuring that specific exceptions are thrown during the execution of your code.
@Test
public void checkError() {
assertThrows(NullPointerException.class, () -> {
throw new NullPointerException();
});
}
Multiply Assertions/Soft Assertions
When testing, there are instances where we need to verify multiple aspects in a single test. This is frequently observed in lengthy tests such as end-to-end (e2e) tests. For example, suppose we have a Cat object, and we wish to confirm that various information is accurate.
Let's take a look at our Cat class:
import lombok.*;
@Data
@Builder
@AllArgsConstructor
public class Cat {
private String name;
private String breedCat;
private String colourFur;
private String patternFur;
private boolean isDomestic;
}
Now, here are the steps for our test:
- Check the name
- Check the breedCat
- Check the colourFur
- Check the patternFur
Suppose the cat we're looking at has the wrong name and patternFur. If we use hard assertions (the regular ones), the test will stop when it finds the first problem. In our case, it would tell us about the wrong name but miss the wrong patternFur.
Here's where soft assertions come in handy. They let us keep checking multiple things even if we find a problem. This gives us a better overall view of what might be wrong in a single test.
When using assertions, sticking to one library is important. Mixing different assertion libraries is not supported. Choose and use the one that fits your needs.
JUnit
JUnit provides the assertAll() method for soft assertions. You can pass multiple assertions as arguments, and it will report all failures.
However, it's crucial to use only JUnit assertions inside assertAll(). Assertions from other test libraries won't work correctly.
@Test
public void checkAssertAll() {
Cat salemCat = Cat.builder()
.name("Salem").colourFur("Black").patternFur("Solid")
.isDomestic(true)
.build();
assertAll("Should be correct houses",
() -> assertEquals("Incorrect", salemCat.getName()),
() -> assertNotNull(salemCat.getBreedCat()),
() -> assertEquals("Solid", salemCat.getPatternFur()),
() -> assertFalse(salemCat.isDomestic())
);
}
In this example, you have 3 failed assertions and you should be informed about all issues.
TestNG
TestNG also supports soft assertions through the SoftAssert class. Create a SoftAssert object, execute assertions on this object, and finally, use the assertAll() method to check all assertions at the end of the test.
@Test
public void checkAssertAll() {
Cat cheshireCat = Cat.builder()
.name("Cheshire").patternFur("Striped").isDomestic(false)
.build();
SoftAssert softAssert = new SoftAssert();
softAssert.assertEquals("Incorrect", cheshireCat.getName());
softAssert.assertNotNull(cheshireCat.getBreedCat());
softAssert.assertEquals("Striped", cheshireCat.getPatternFur());
softAssert.assertTrue(cheshireCat.isDomestic());
softAssert.assertAll();
}
For the above example, three failed assertions exist.
AssertJ
AssertJ also has implemented soft assertions. Similar to TestNG, we have to use the SoftAssrtions object.
@Test
public void yourTestMethod() {
Cat nyanCat = Cat.builder()
.name("Nyan Cat").colourFur("Rainbow").patternFur("Striped")
.isDomestic(true)
.build();
SoftAssertions softAssertions = new SoftAssertions();
softAssertions.assertThat(nyanCat.getName()).startsWith("No");
softAssertions.assertThat(nyanCat.getBreedCat()).isNotNull();
softAssertions.assertThat(nyanCat.getColourFur()).isEqualTo("Rainbow");
softAssertions.assertThat(nyanCat.isDomestic()).isFalse();
softAssertions.assertAll();
}
Three failed assertions are included in the test report.
Hamcrest/Truth
Hamcrest and Truth do not provide a built-in mechanism for soft assertions. To implement soft assertions, you may need to manually catch and accumulate assertion errors.
Stacked Assertions
Sometimes, you may need to verify multiple conditions for a single object, particularly in longer end-to-end (e2e) tests. Stacked assertions can be beneficial in such scenarios, enhancing the clarity of your tests. It's important not to confuse stacked assertions with soft assertions; they address different aspects of testing.
Let's explore examples in two test libraries, AssertJ and Hamcrest, as they are the ones that support stacked assertions. Other libraries do not currently include this feature.
AssertJ
AssertJ has a very simple syntax for stacked assertions. You just have to add the next assertions to the previous one. All assertions should be satisfied. Also, they are hard assertions, so after the first failed tests are terminated you are only noticed about the first failure.
Let’s look at the example. Used assertions are from “Collections and Arrays Assertions”
@Test
public void checkStockedAssertions(){
var housesList = List.of(
"Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin"
);
assertThat(housesList)
.hasSize(4)
.allMatch(e -> e.length() >= 9)
.anyMatch(e -> e.startsWith("R"))
.doesNotContain("Hogwart")
.doesNotHaveDuplicates();
}
Hamcrest
Hamcrest provides logical matchers, expanding the possibilities for creating stacked assertions when compared to AssertJ. Using nested expressions by combining multiple logical matchers makes it even more flexible.
Logical matchers:
- not() works as logical operation NOT
- anyOf() works as logical operation OR
- allOf() works as logical operation AND
- either().or() works as a logical operation OR for two matches
- both().and() works as a logical operation AND for two matches
Now, let's explore one-argument and two-argument logical matchers.
@Test
public void checkLogicMatchers_twoParameters() {
String charm = "Wingardium Leviosa";
String startingTxt = "Win";
String endingTxt = "os";
// [A], [B], [C] will be matchers
// [A] AND (NOT [B])
// assert will pass, if A is true and B is false
assertThat(charm,
both(startsWith("Win")).and(not(endsWith("os"))));
// [A] OR [B]
// assert will pass if at least [A] or [B] will be true
assertThat(charm,
either(startsWith("Win")).or(endsWith("os")));
}
There we have logical matchers with more possible arguments. There is also a “contains()” matcher for iterable. It works similarly to allOf(), but only for the iterable subjects.
@Test
public void checkLogicMatchers() {
var housesList = List.of(
"Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin"
);
// all matchers should be satisfied
assertThat(housesList, allOf(
iterableWithSize(4),
hasItem("Slytherin"),
everyItem(is(notNullValue()))
));
// at least one matcher should be satisfied
assertThat(housesList, anyOf(
iterableWithSize(3),
hasItem("Hogwart"),
everyItem(is(notNullValue()))
));
}
Custom assertions
Sometimes, implemented assertion methods may not cover specific cases. For such scenarios, you can create custom assertions.
JUnit/TestNG
In JUnit and TestNG, it's not possible to create custom assertions directly. You are limited to using the implemented ones provided by the frameworks.
Hamcrest
Hamcrest allows the creation of custom assertions by defining a class that extends TypeSafeMatcher<T>, where T is the type of the object you want to verify. Here's an example for a custom assertion checking if an Integer is a magic number:
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
public class IsMagicNumber extends TypeSafeMatcher {
@Override
protected boolean matchesSafely(Integer integer) {
return (integer % 9) == 1;
}
@Override
public void describeTo(Description description) {
description.appendText("a magic number");
}
public static Matcher isMagicNumber() {
return new IsMagicNumber();
}
}
With this custom assertion, you can now use it in your tests:
@Test
public void checkMagicNumber() {
int magicNumber = 1234;
int notMagicNumber = 1235;
assertThat(magicNumber, isMagicNumber());
assertThat(notMagicNumber, not(isMagicNumber()));
}
AssertJ
AssertJ also provides the possibility to create custom assertions. You need to create a class that extends AbstractAssert<YourClass, T>, where T is the type of object you want to verify. Alternatively, you can extend a subclass of AbstractAssert.
import models.Cat;
import org.assertj.core.api.AbstractAssert;
public class CatAssert extends AbstractAssert{
public CatAssert(Cat actual) {
super(actual, CatAssert.class);
}
public static CatAssert assertThat(Cat actual) {
return new CatAssert(actual);
}
public CatAssert hasName(String name) {
isNotNull();
if (!actual.getName().equals(name)) {
failWithMessage("Expected cat's name to be <%s> but was <%s>", name, actual.getName());
}
return this;
}
}
In this example, we are verifying the Cat class and its parameter name. For more explanations, you can refer to the official docs.
Now, let’s explore the usage of our assertion.
@Test
public void checkCustomAssertions() {
Cat nyanCat = Cat.builder()
.name("Nyan Cat").colourFur("Rainbow").patternFur("Striped")
.isDomestic(true)
.build();
assertThat(nyanCat).hasName("Nyan Cat");
}
@Test
public void checkFailCustomAssertions() {
Cat nyanCat = Cat.builder()
.name("Nyan Cat").colourFur("Rainbow").patternFur("Striped")
.isDomestic(true)
.build();
assertThat(nyanCat).hasName("Incorrect");
}
The second test will fail with a custom message.
Truth
In Truth, you can create custom assertions by extending the Subject class. Inspired by the documentation available here: https://truth.dev/extension, you can find more information on creating private methods.
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
import models.Cat;
import org.checkerframework.checker.nullness.qual.Nullable;
public class CatSubject extends Subject {
public static CatSubject assertThat(@Nullable Cat cat) {
return assertAbout(cats()).that(cat);
}
private CatSubject(FailureMetadata failureMetadata, @Nullable Cat subject) {
super(failureMetadata, subject);
this.actual = subject;
}
public static Subject.Factory cats() {
return CatSubject::new;
}
private final Cat actual;
public void hasName(String name) {
actual.getName().equals(name);
}
}
Here is an example of how to use the created assertions:
@Test
public void checkCustomAssertions() {
Cat goose = Cat.builder()
.name("Goose").breedCat("Flerken").colourFur("Brown/Orange")
.patternFur("Solid").isDomestic(false)
.build();
assertThat(goose).hasName("Goose");
}
Summary
This article dives into the world of assertions, covering various testing aspects. While it provides a comprehensive overview, it's crucial to note that the topic is extensive, and only some assertion types are discussed in detail. The range of assertions includes basics and more complex elements like objects and collections, along with unexplored types such as dates, databases, or paths, which can be explored in libraries’ documentation.
The article highlights their unique features, highlighting popular testing frameworks like JUnit, TestNG, Hamcrest, AssertJ, and Truth. Each library has its pros and cons. When choosing a library for testing purposes, considering all aspects is vital. This article aims to guide developers through the diverse realm of assertion libraries, helping them make informed decisions based on their specific testing needs.