Unit testing is when you test individual components of a software system in isolation. In this context, a component could refer to a module, method, or class.

The goal of unit testing is to ensure that each component works as expected on its own. Good unit tests that have been incorporated into automated build processes can help you detect bugs early, encourage good code design practices, and improve overall code quality.

To excel at writing unit tests in Java, you have to master JUnit assertions. In general, assertions are statements in unit tests that check whether the actual result of a code operation matches the expected result.

In the world of JUnit, you have a plethora of JUnit assertions to choose from. From assertEquals to assertSame and assertTrue to assertThrows, it’s hard to choose the assertion to use and when to use it. Moreover, you need to know how to use the assertions in combination with frameworks like Hamcrest to make your unit tests more comprehensive and readable. In this article, you’ll learn about all these topics and review some code samples to show you different JUnit assertions in action.

Basic JUnit Assertions

Let’s set the stage by defining some classes that you’ll use as the basis for examples throughout this article.

In this scenario, let’s suppose you want to create an application that models a store with a number of products in inventory. You’ll define the interfaces Store and Product like this:

import java.util.*;

public interface Store {

	Map<Integer, Product> getProducts();
	void addProduct(Product product);
	void removeProduct(int productId);
	void sellProduct(int productId, int quantity);

}

public interface Product {

	int getId();
	String getName();
	double getPrice();
	int getQuantity();
	void setQuantity(int quantity);

}

From there, you can define a specific store called FooStore:

class FooStore implements Store {

	private final Map<Integer, Product> products;

	public FooStore() {
		products = new HashMap<>();
	}

	@Override
	public Map<Integer, Product> getProducts() {
		return products;
	}

	@Override
	public void addProduct(Product product) {
		products.put(product.getId(), product);
	}

	@Override
	public void removeProduct(int productId) {
		products.remove(productId);
	}

	@Override
	public void sellProduct(int productId, int quantity) {
		Product product = products.get(productId);
		if (product != null) {
			if (product.getQuantity() > quantity) {
				product.setQuantity(product.getQuantity() - quantity);
			}
		}
	}
}

Please note: This implementation may include a few bugs, but that’s why we need to test it thoroughly.

The FooStore sells FooProduct products, which you can define as follows:

class FooProduct implements Product {

	private final int id;
	private final String name;
	private final double price;
	private int quantity;

	public FooProduct(int id, String name, double price, int quantity) {
		this.id = id;
		this.name = name;
		this.price = price;
		this.quantity = quantity;
	}

	@Override
	public int getId() { return id; }
	@Override
	public String getName() { return name; }
	@Override
	public double getPrice() { return price; }
	@Override
	public int getQuantity() { return quantity; }
	@Override
	public void setQuantity(int quantity) { this.quantity = quantity; }
}

Here, you have a FooStore that sells FooProducts, and each of these classes has implementations for the methods defined by their respective interfaces.

With the scenario in place, let’s take a look at some JUnit assertions and how you can write test cases for the sample application. The following examples were written using JUnit version 5.8.2.

Using assertTrue and assertFalse

assertTrue and assertFalse are two basic JUnit assertions that all Java developers should know. These assertions check the result of Boolean operations.

For example, you can use assertTrue to test that the addProduct method does indeed add to the FooStore’s products map:

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

class JUnitAssertsTests {

	@Test
	void addProduct_testingAssertTrue() {
		FooStore fooStore = new FooStore();
		FooProduct fooProduct = new FooProduct(1234, "test", 9.99, 1);
		
		fooStore.addProduct(fooProduct);
		assertTrue(fooStore.getProducts().containsKey(1234));
	}
}

In a similar vein, you can use assertFalse to check that the map no longer contains fooProduct if you add it; then remove it with the removeProduct method:

@Test
void addThenRemoveProduct_testingAssertFalse() {
	FooStore fooStore = new FooStore();
	FooProduct fooProduct = new FooProduct(1234, "test", 9.99, 1);

	fooStore.addProduct(fooProduct);
	assertTrue(fooStore.getProducts().containsKey(1234));

	fooStore.removeProduct(1234);
	assertFalse(fooStore.getProducts().containsKey(1234));
}

Using assertEquals and assertNotEquals

Two other assertions that you’ll use frequently are assertEquals and assertNotEquals. These assertions are most commonly used to verify that a method returns the expected result. The two arguments that you supply to these assertions are evaluated for equality. Here, equality means that the object’s equals() method returns true (ie a.equals(b)).

For example, you can use assertEquals to test that the product you add with the addProduct method contains the expected parameters:

@Test
void addProduct_testingAssertEquals() {
	FooStore fooStore = new FooStore();
	FooProduct fooProduct = new FooProduct(1234, "test", 9.99, 1);

	fooStore.addProduct(fooProduct);

	Product actualProduct = fooStore.getProducts().get(1234);
	assertEquals("test", actualProduct.getName());
	assertEquals(9.99, actualProduct.getPrice());
	assertEquals(1, actualProduct.getQuantity());
}

A tool like Diffblue Cover can be invaluable in helping you generate unit tests automatically. For instance, with the following Diffblue Cover CLI command, you can generate a test for addProduct:

dcover create com.example.FooStore.addProduct

Diffblue Cover quickly produces the following test. It makes use of the Mockito framework to generate mocks and uses the assertEquals assertion:

@Test
void testAddProduct() {

	// Arrange
	FooStore fooStore = new FooStore();
	Product product = mock(Product.class);
	when(product.getId()).thenReturn(1);

	// Act
	fooStore.addProduct(product);

	// Assert
	verify(product).getId();
	assertEquals(1, fooStore.getProducts().size());
}

In this example, Diffblue Cover mocks the Product class to avoid having to create a real instance during testing.

You can also test that the sellProduct method reduces the quantity of the product as expected:

@Test
void sellProduct_testingAssertEquals() {
	FooStore fooStore = new FooStore();
	FooProduct fooProduct = new FooProduct(1234, "test", 9.99, 5);

	fooStore.addProduct(fooProduct);

	Product actualProduct = fooStore.getProducts().get(1234);
	assertEquals(5, actualProduct.getQuantity());

	fooStore.sellProduct(1234, 5);
	assertEquals(0, actualProduct.getQuantity()); // All products sold!
}

As you can see, with the current implementation, this test actually fails. Upon closer inspection, the second if statement in the sellProduct method should be a >= instead of a >:

if (product.getQuantity() >= quantity) {
	product.setQuantity(product.getQuantity() - quantity);
}

After making this change, your test should pass.

As for the assertNotEquals assertion, let’s consider a scenario that you want to test when a product goes on sale:

@Test
void addThenAddProduct_testingAssertNotEquals() {
	FooStore fooStore = new FooStore();
	FooProduct fooProduct = new FooProduct(1234, "test", 9.99, 1);

	fooStore.addProduct(fooProduct);

	Product actualProduct = fooStore.getProducts().get(1234);
	assertEquals(9.99, actualProduct.getPrice());

	FooProduct fooProductSale = new FooProduct(1234, "test", 6.99, 1);
	fooStore.addProduct(fooProductSale); // Should replace fooProduct

	Product actualProductSale = fooStore.getProducts().get(1234);
	assertEquals(1, actualProductSale.getQuantity());
	assertNotEquals(9.99, actualProductSale.getPrice()); // Expected: 6.99
}

Please note: You could also use assertEquals here to check that the product’s price is equal to the sale price of $6.99 USD:

assertEquals(6.99, actualProductSale.getPrice());

However, in this instance, you chose assertNotEquals to emphasize that the price should not be equal to the original price whenever there is a sale. The test could be rewritten like this:

assertTrue(actualProductSale.getPrice() < 9.99);

This test is perhaps even better than assertNotEquals since it asserts that the price must be lower during sales.

As you can see, there are many ways to write tests to achieve similar results. Different assertions emphasize different things; so choose the assertion that most closely aligns with the underlying intention of your tests.

Using assertSame and assertNotSame

The assertSame assertion is stricter than assertEquals: it checks to see if the two objects compared are one and the same. To evaluate sameness, assertSame uses the == operator (ie a == b).

For example, you can verify that the FooProduct you add via addProduct is exactly the same FooProduct retrieved from the FooStore products map:

@Test
void addProduct_testingAssertSame() {
	FooStore fooStore = new FooStore();
	FooProduct fooProduct = new FooProduct(1234, "test", 9.99, 1);

	fooStore.addProduct(fooProduct);

	Product actualProduct = fooStore.getProducts().get(1234);
	assertSame(fooProduct, actualProduct);
}

In the previous example, using assertEquals in place of assertSame would work as well since, in addition to being identical to each other, both objects are also equal. In other words, the default fooProduct.equals(actualProduct) method would return true. To see the difference between assertEquals and assertSame, consider the following test: first override the equals() implementation for FooProduct:

@Override
public boolean equals(Object  o) {
	FooProduct other = (FooProduct) o;
	return this.id == other.getId() &&
		this.name.equals( == other.getName()) &&
		this.price == other.getPrice() &&
		this.quantity == other.getQuantity();
}

Then, consider the following test:

@Test
void differenceBetweenAssertSameAssertEquals() {
	Product product1 = new FooProduct(1, "test", 0.99, 5);
	Product product2 = new FooProduct(1, "test", 0.99, 5);
	assertNotSame(product1, product2);
	assertEquals(product1, product2);
}

In this example, the assertNotSame assertion is true because the two objects are not identical. The assertEquals assertion is also true because of the way we defined the equals() method: as long as this evaluates to true, the two objects are equal. In other words, product1 and product2 are equal, but they are not the same.

Using assertNull and assertNotNull

Lastly, let’s review assertNull and assertNotNull. You can use these assertions to check whether objects are empty or non-empty.

For example, let’s check that the products are correctly added to the products map using these assertions:

@Test
void addProduct_testNullAndNotNull() {
	FooStore fooStore = new FooStore();
	FooProduct fooProduct = new FooProduct(1234, "test", 9.99, 1);

	fooStore.addProduct(fooProduct);
	assertNull(fooStore.getProducts().get(12345));
	assertNotNull(fooStore.getProducts().get(1234));
}

This works because the Map.get() method returns null if no entry in the map exists with the given key.

Assertions for Exceptions

In addition to testing for expected results, you’ll also often have to test for expected exceptions. For example, let’s expand our sellProduct implementation to account for cases where you don’t have enough units to sell.

Start by creating a custom InsufficientInventoryException class for this scenario:

public class InsufficientInventoryException extends Exception {
	public InsufficientInventoryException(String errorMessage) {
		super(errorMessage);
	}
}

Then throw this exception in sellProduct (Please note: You have to declare the exception in the signature, which means you have to update the interface method’s signature as well):

	@Override
	public void sellProduct(int productId, int quantity) throws  InsufficientInventoryException {
	Product product = products.get(productId);
	if (product != null) {
		if (product.getQuantity() >= quantity) {
			product.setQuantity(product.getQuantity() - quantity);
		} else {
			throw new InsufficientInventoryException(
				"Not enough inventory to sell " + Integer.toString(quantity) +
				" units of productId " + Integer.toString(productId));
		}
	}
}

Make sure that your code throws an InsufficientInventoryException when it reaches this case.

Using assertThrows

After you’ve added the exception, you can test that the InsufficientInventoryException is thrown using the assertThrows JUnit assertion. The syntax for this is as follows:

@Test
void sellProduct_testingAssertThrows() {
	FooStore fooStore = new FooStore();
	FooProduct fooProduct = new FooProduct(1234, "test", 9.99, 5);

	fooStore.addProduct(fooProduct);
	Product actualProduct = fooStore.getProducts().get(1234);
	assertEquals(5, actualProduct.getQuantity());

	assertThrows(InsufficientInventoryException.class, () -> {
		fooStore.sellProduct(1234, 6); // We expect this call to throw InsufficientInventoryException.
	});
}

The first argument to use assertThrows is the type of exception you expect to be thrown. The second argument is a lambda expression that calls the method that throws the exception.

Please note: In the method signature, you don’t have to declare throws InsufficientInventoryException. This is because the assertThrows assertion automatically consumes the caught exception by default.

Custom Assertions and Hamcrest Matchers

In some scenarios, you may want to create custom assertions that aren’t natively available in the JUnit framework. For example, suppose you want to test whether a list is sorted. You might create a custom assertSorted assertion in a testing utility class (ie TestUtil.java) as follows:

public static void assertSorted(List<Integer> list) {
    for (int i = 0; i < list.size() - 1; i++) {
        assertTrue(list.get(i) > list.get(i + 1));
    }
}

Then you can write a unit test that calls this assertSorted method just like you would any other JUnit assertion:

@Test
public void testSort() {
    List<Integer> list = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
    Collections.sort(list);
    TestUtil.assertSorted(list);
}

Understanding Hamcrest Matchers

For additional customizability in your unit tests, consider using Hamcrest. Hamcrest is a popular framework that you can use together with JUnit to significantly improve the readability of your tests. For instance, consider the assertion you wrote earlier:

assertEquals(5, actualProduct.getQuantity());

If you were to write this assertion using Hamcrest, it would look like this:

assertThat(actualProduct.getQuantity(), equalTo(5));

This assertion reads much closer to plain English: “Assert that actualProduct’s quantity is 5.” Here, assertThat is a special Hamcrest construct for creating test assertions. equalTo() is a Hamcrest matcher—a construct used to verify results.

is() is another common Hamcrest matcher. is() checks for object equality, similar to the assertEquals JUnit assertion. For example, here’s one of your previous tests using Hamcrest:

@Test
void addProduct_testingAssertSame_Hamcrest() {
	FooStore fooStore = new FooStore();
	FooProduct fooProduct = new FooProduct(1234, "test", 9.99, 1);

	fooStore.addProduct(fooProduct);
	Product actualProduct = fooStore.getProducts().get(1234);
	assertThat(actualProduct, is(fooProduct));
}

You can also nest Hamcrest matchers. This is most commonly used with matchers like is() and not():

@Test
void addProduct_testNullAndNotNull_Hamcrest() {
	FooStore fooStore = new FooStore();
	FooProduct fooProduct = new FooProduct(1234, "test", 9.99, 1);

	fooStore.addProduct(fooProduct);

	assertThat(fooStore.getProducts().get(12345), is(nullValue()));
	assertThat(fooStore.getProducts().get(1234), is(not(nullValue()))); // is(notNullValue()) also okay
}

Combining JUnit Assertions for Complex Tests

Sometimes, you may want to combine multiple JUnit assertions. This is particularly useful for operations where you have to verify that multiple things are all true to determine a test’s success or failure. In JUnit, you can use the assertAll assertion to achieve this.

For instance, you can verify that all properties of your FooProduct are as expected with the following test (note that Hamcrest is used in the last two nested assertions as well):

@Test
void addProduct_verifyAllProperties_assertAll() {
	FooStore fooStore = new FooStore();
	FooProduct fooProduct = new FooProduct(1234, "test", 9.99, 1);

	fooStore.addProduct(fooProduct);

	assertAll("fooProduct should exist and contain the correct parameters",
		() -> assertTrue(fooStore.getProducts().containsKey(1234)),
		() -> assertEquals(1234, fooStore.getProducts().get(1234).getId()),
		() -> assertEquals("test", fooStore.getProducts().get(1234).getName()),
		() -> assertThat(fooStore.getProducts().get(1234).getPrice(), equalTo(9.99)),
		() -> assertThat(fooStore.getProducts().get(1234).getQuantity(), equalTo(1))
	);
}

Since JUnit evaluates all assertions in a single assertAll, you can potentially use it to cut down on debugging times. If you had separate assertions, the test would fail on the first assertion and you wouldn’t know the result of any subsequent assertions. However, be mindful that combining assertions could overcomplicate your tests and make them harder to read.

Conclusion

In this article, you learned about all kinds of different JUnit assertions and saw code examples for each of them. Here’s a recap of the most common ones:

  • assertTrue and assertFalse: used to check the results of Boolean operations.
  • assertEquals and assertNotEquals: used to check that the actual result of an operation is what you expect. This assertion checks for object equality, meaning that JUnit uses the equals() method for evaluation.
  • assertSame and assertNotSame: used to check that the actual object is the same as the expected object. This assertion checks for object identity, meaning that JUnit uses the == operation for evaluation.
  • assertNull and assertNotNull: used to check that objects are empty or non-empty.
  • assertThrows: used to verify that a particular type of exception is thrown by an operation.

In addition to learning about the assertion earlier, you also saw how you can use the Hamcrest framework to construct more expressive tests as well as how to use assertAll to combine related assertions into a single check. Being familiar with these tools is imperative for writing strong unit tests, which will improve the overall code quality across your codebase.

While JUnit is an essential tool to master, any Java developer will tell you that coming up with robust unit tests still takes time (at least 20% is typical but it’s often a lot more).

To further automate your unit testing and to give more productive time back to your developers, consider using Diffblue Cover. Cover is a tool that writes complete unit tests for Java code without any developer input. You can use it at any level, from an individual method to an entire project; it can even run completely automatically in a CI pipeline. Better still, Cover doesn’t just write tests for you - it also updates them automatically every time you change your code, and provides detailed coverage reports across your entire codebase. You can try Cover now - including the full power of the CLI option - with this fourteen-day free trial.

Or if you’d just like to learn more about effective JUnit assertions, we’ve got more on the subject here.