Fluent Assertions with AssertJ

Billy Korando Effective Automated Testing With Spring Series, Java, Series Leave a Comment

This article is part of a blog series on automated testing in promotion for my new Pluralsight course Effective Automated Testing with Spring.

I recently gave a presentation to my Keyhole team members about JUnit 5. I started off the presentation by covering the importance of automated testing, how lack of automated testing affects an organization’s ability to deliver code to production, and how without automated testing you are building legacy.

I pointed out two key benefits of automated testing: confidence you are fixing what you set out to fix, and confidence you are not introducing a new bug. A co-worker however pointed out an important third benefit of automated testing: providing living documentation.

My co-worker made the very good point that automated testing can do more than just checking for code correctness. It can also provide valuable documentation for current and future developers on a project.

In this article, we look at how using AssertJ can make automated tests easier to read and write. We take a look at how AssertJ improves the readability of assertions in test cases, as well as how it helps make the task of comparing complex objects and performing list validations easier to read and write. The goal is that when tests are easier to read and write, it will hopefully encourage developers to write more tests (i.e. documentation).

Tests as Living Documentation

When I bring up the topic of documentation with developers, there are, of course, audible groans. But the examples they often point to are design docs, javadoc, and code comments, as well as good names for classes, methods, and variables as examples of documentation. These are certainly all important forms of documentation.

Automated testing, however, is a rarely cited form of documentation. This is unfortunate as automated tests provides something those other forms of documentation don’t and that is living documentation.

What do I mean when I say living documentation? I mean documentation that will continually be checked for validity as a project changes and grows over time. Take the below example:

@Test
public void testFindCustomersByFNameAndLName() {
	this.entityManager.persist(bojack);
	this.entityManager.persist(todd);
	this.entityManager.persist(princess);
	Iterable <Customer> customers = repo.findCustomersByFirstNameAndLastName("Princess", "Caroline");

	int count = 0;
	for (Customer repoCustomer : customers) {
		assertEquals("Princess", repoCustomer.getFirstName());
		assertEquals("Caroline", repoCustomer.getLastName());
		assertEquals("Cat", repoCustomer.getMiddleName());
		assertTrue(repoCustomer.getId() > 0L);
		assertNull(repoCustomer.getDateOfLastStay());
		assertNull(repoCustomer.getSuffix());
		count++;
	}
	assertEquals(1, count);
}

With every build of the application, this test is being run and it is telling me the following:

  • There is functionality for looking up a customer by their first and last name
  • This functionality returns an Iterable of the type Customer

If a change is made that breaks this functionality (whether by deliberate choice or accident), this test will begin to fail. When a test fails, a developer will either need to:

  • Remove the test because the functionality is no longer provided (i.e. remove no longer valid documentation).
  • Update the test to reflect the current state of the functionality (i.e. update the documentation).
  • Fix the introduced bug to make sure the application meets design requirements (i.e. make sure the application matches existing documentation).

Regardless of the change made, once the test suite is passing, the documentation is once again up to date.

See Also:  Spring Boot Profiles: A Strategic Way to Configure Applications

Fluent and Consistent Asserting with AssertJ

While I’m a huge fan JUnit, I’m less of a fan of how it handles asserting.

One of the most commonly used assertions in JUnit, assertEquals, is unfortunately one of the hardest to read. No way is it inherently superior to the other, but I have always personally felt that assertEquals(actual, expected) made more sense, however assertEquals is actually implemented as assertEquals(expected, actual). This has on multiple occasions caused me headaches from really confusing, failed assertion messages when I or another developer implemented assertEquals backwards.

A couple of years ago a friend turned me on to AssertJ. It takes a much more clear and consistent approach to writing assertions.

In JUnit, checking for equivalency looks like the below:

assertEquals("Room type: NOT FOUND not found!", e.getMessage())

Comparably, checking for equivalency in AssertJ looks like this:

assertThat(e.getMessage()).isEqualTo("Room type: NOT FOUND not found!");

In JUnit you have several different assertions to choose from and they are all implemented slightly differently. For boolean values, you have assertTrue or assertFalse. For null, assertNull().

But, in AssertJ, these assertions would instead be written as:

assertThat(value).isTrue();
assertThat(value).isFalse();
assertThat(value).isNull();

In AssertJ, all assertions begin with assertThat(), which creates a more consistent experience when writing assertions (as well as reading them).

Comparing Complex Objects

Comparing two objects is a frequent task when writing test cases. A call is made to a DAO or another service, and you want to validate that the returned object has all the correct values. Such a test case would typically be implemented like this:

@Test
public void testAddRoom() {
   RoomRepo repo = mock(RoomRepo.class);
   when(repo.save(any())).thenReturn(new Room(1L, "100", "Single", new BigDecimal(149.99)));
   RoomServiceImpl service = new RoomServiceImpl(repo, roomTypes);

   Room newRoom = service.addRoom(new Room());
   assertEquals(1L, newRoom.getId());
   assertEquals("100", newRoom.getRoomNumber());
   assertEquals("Single", newRoom.getRoomType());
   assertEquals(new BigDecimal(149.99), newRoom.getRoomRate()));
}

This process is made easier with AssertJ by using the isEqualToComparingFieldByField assertion. So instead of four assertions (like in the above example), a single assertion can be used instead:

assertThat(originalRoom).isEqualToComparingFieldByField(returnedRoom);

AssertJ also offers several different takes on this functionality which allow for easy declaring of which fields should be used in a comparison:

assertThat(originalRoom).isEqualToComparingOnlyGivenFields(returnedRoom, "roomType", "roomRate");

Or, alternatively, fields that should not be used in a comparison:

assertThat(originalRoom).isEqualToIgnoringGivenFields(returnedRoom, "id");

These alternatives can be useful if the equals implementation doesn’t suit the purposes of the test.

An important note about this feature is that the assertion compares values by the names of the fields in the objects, but not their types. This can be useful when doing data transformation between two types. Though, this depends upon the names of the fields being the same between the two objects being compared.

Validating Lists

Validating lists can be a very painful task in automated testing. Before I started using AssertJ, I often did only superficial checks of lists (size, an item or two), rather than really checking to make sure the list is what I am expecting it to be.

See Also:  What's New in JUnit 5.1

Validating lists in AssertJ is much easier when using the extracting() and contains() features. As an example, say I wanted to check all the first names of customers returned in a list. I would write out that assertion to look like this:

assertThat(customers).extracting("firstName").contains("BoJack", "Princess", "Todd");

If I wanted to check both firstName and lastName, I can use assertJ’s tuple feature.

assertThat(customers).extracting("firstName", "lastName").contains(tuple("BoJack", "Horseman"), tuple("Princess", "Carolyn"), tuple("Todd","Chavez"));

As is often the case, AssertJ offers several takes on how strictly you want to check the contents of the list. contains just makes sure all items are present. You can also make sure the list only contains the checked for values with containsOnly. Or, you can check for order with containsExactly.

Mixing AssertJ with JUnit 5

One of the coolest new features added in JUnit 5 is assertAll. This helps resolve (what can be) a really frustrating scenario of having to run a test multiple times to resolve all assertion failures. assertAllcan instead be used to collect all assertion failures and report them back in a single run of a test case.

AssertJ already has similar functionality with soft assertions, however I feel assertAll is a bit easier to use and more straightforward in intent. What is nice is AssertJ assertions can be added directly into assertAll without issue. So if you need to assert multiple values in tests and other options like isEqualToComparingFieldByField don’t suit your needs, assertAll is a good alternative.

assertAll(() -> assertThat(repoCustomer.getFirstName()).isEqualTo("Princess"),
() -> assertThat(repoCustomer.getLastName()).isEqualTo("Caroline"),
() -> assertThat(repoCustomer.getMiddleName()).isEuqalTo("Cat"),
() -> assertThat(repoCustomer.getId() > 0L).isTrue(),
() -> assertThat(repoCustomer.getDateOfLastStay()).isNull(),
() -> assertThat(repoCustomer.getSuffix()).isNull());

Credit: Rafał Borowiec “JUnit 5 Meets AssertJ.”

Conclusion

Automated testing is a critical step in the development cycle. This is not only for serving as a check to ensure we don’t push bugs to production but to provide valuable documentation for the applications we write.

Using AssertJ can make the process of writing automated tests a little easier — it can also make those tests easier to read as well. Increasing the value of tests and decreasing the cost of writing them are both important steps in encouraging developers to write more automated tests.

Code samples used in this article can be found here. Make sure to see the other posts in this series below.

Automated Testing Series

  1. Without Automated Testing You Are Building Legacy
  2. Four Common Mistakes That Make Automated Testing More Difficult
  3. Encouraging Good Behavior with JUnit 5 Test Interfaces
  4. Conditionally Disabling and Filtering Tests in JUnit 5
  5. What’s New in JUnit 5.1
  6. This post – > Fluent Assertions with AssertJ
  7. What’s New in JUnit 5.2

About the Author
Billy Korando

Billy Korando

Twitter

Billy Korando is a software consultant with Keyhole Software in Kansas City and currently working on-site with USDA. Billy has over a decade of experience in Java web development and has worked in the exciting industries like insurance, shipping, healthcare, finance, and government.


Share this Post

What Do You Think?