Assert but Verify

Donald Raab
7 min read6 days ago

--

An example of the convenience of inheritance in Java gone wrong

Photo by Mark König on Unsplash

Statue extends Bird

Please file this in the “Statue extends Bird” category of inheritance lessons in Java. Consider this a cautionary tale of leveraging an inheritance relationship between utility classes purely for a convenience. It’s hard to remove Bird once it has been extended as Statue.

When we think of inheritance in Java, we usually think of classes that override instance methods from a parent class. The parent class may define some common attributes and behaviors, and the child class can add additional attributes and behaviors in addition to being able to use the parents attributes with a suitable scope defined and specialize the behaviors that were defined in the parent, also with a suitable scope defined.

We don’t usually think of inheriting static methods defined in utility classes. In this blog, I will share an example of a static utility class that extends another static utility class.

The Futility of Utility

Don’t create a static utility class that extends another static utility class. Even if the convenience feels overwhelmingly amazing, resist the temptation. If you are a library developer creating a static utility class, you can help prevent others from the potential pain by making the class final.

I have written and spoken about what I call the futility of utility before, but this is a new kind of futility of a utility class. When you build Statue and extend Bird, you potentially accumulate $hi+ that you don’t define, but that you and your clients become increasingly dependent on. The Bird doesn’t know about the Statue. Over many years, it may unknowingly add new methods on the Statue. Those methods harden over time so the Statue may look like is an extension of the Bird. The Statue is not the Bird, but the Statue was designed to be Bird.

The methods defined on the Bird will probably be great, appropriate, and extremely useful. The Statue extending the Bird is not the decision of the Bird, even if the Bird allows itself to be extended. It is the decision of the designers of the Statue. They have designed a BirdButStatue utility class.

As a developer who has designed Java utility classes over the year, I try and prevent others from making the same mistakes I have made or allowed, by making utility classes I add final. This makes it clear that the utility class is to only be used, as the final keyword makes it impossible to extend. If you find a utility class in Eclipse Collections that is not final, please report it as a potential bug via the GitHub issue tracker. Java library designers may have good reasons to not make a utility class final, and hopefully document those reasons and leave guidance to future developers who depend on the utility classes.

AssertButVerify

There is a module in Eclipse Collections that is published as a separate .jar with a utility class with only static methods named Verify. Verify extends Assert, which also only has static methods that are public. Verify can do everything that Assert can do. You can call public static methods on Verify, which are actually defined on Assert, and may or may not be “hiding” as specifically defined methods on Verify.

This is the current class definition for the Verify utility class in Eclipse Collections. Thankfully, no class can further extend Verify. Verify has been designed this way for over a decade, and has had absolutely no issues.

public final class Verify extends Assert

Why does Verify extend Assert?

Verify was a utility class created a long time ago to make it easy to assert specific things about Collection and Iterable types. The assertions on Verify have intention revealing names about what is being asserted about various collection types. A complete list of the methods on Verify can be seen in a table below. The methods on this class are used extensively for testing collections in Eclipse Collections. A convenience decision was made to have Verify extend the Assert class, so that we didn’t have to switch between using Verify and Assert for common assertions in test cases (e.g. assertEquals, assertNull). Verify is Assert, but also Verify. This was convenient for use in Eclipse Collections, because we didn’t have to remember which assertion methods were on Assert and which were on Verify, and could just import one class — Verify. When we open sourced Eclipse Collections and made the class public in the eclipse-collections-testutils.jar, our hands became unknowingly and unfortunately tied with this old inheritance dependency decision. Users of the library may have been potentially using the static methods defined on Assert that are available via Verify. This has not caused any issues for Eclipse Collections users or library developers, until now. I will explain what the issue is in the next section.

The following table shows the list of distinct and shared methods between Verify and Assert. Assert has no distinct methods in this table, because Verify extends Assert. If I drew a Venn Diagram to show the relationship, Assert would be completely contained within Verify.

As can be seen by the table above, Verify has a large number of distinct assert methods (165) for developers to use. These methods are extremely useful if you write a lot of collection tests. Bolting an additional 56 methods was purely convenience so we didn’t need to remember whether a method was on Verify or if it was on Assert. We got all the methods in one place. One less import! Yay!

This turns out to be a convenience gone wrong. Thankfully, we will be fixing this unfortunate design decision in the Eclipse Collections 12.0 release, when we are also upgrading to JUnit 5.

All Your Assertions R Belong to Us

We are looking to upgrade from JUnit 4 to JUnit 5 in Eclipse Collections, and this is now causing some short-term pain for the developers working on the library. We have no quick and easy way out of this decades old decision, but there is a path to correcting this convenience design decision, and it is being worked on now.

Eclipse Collections is in the process of upgrading to JUnit 5. As seen above, Verify has a public API that currently extends the public API of Assert. The type junit.org.Assert is a utility class in JUnit 4. There is a new utility class in JUnit 5 that should be used instead of Assert named Assertions, which we have been migrating all of our tests to. What should we do with Verify? Should it be changed to extend Assertions in JUnit 5?

Old Statue seeking new BiggerBird to supply convenience

No, No, No. Although the Assertions class does not prevent Verify from extending it, there is very helpful guidance in the JavaDoc of Assertions from the JUnit team.

Helpful JavaDoc in Assertions class in JUnit 5

We decided even before reading this guidance, that we would break the inheritance dependency between Assert and Verify, and not repeat the same convenience mistake with Assertions. If Verify were changed to extend Assertions, we would have replaced a pigeon-sized bird with an ostrich-sized bird. The Assertions class would add an additional 265 methods to the Verify class if we extended it. This is in addition to the 26 methods we would need to add by hand that Assertions doesn’t have in common with the Assert class. Imagine if we did this what it might be like to eventually upgrade Verify to JUnit 6. Ouch!

We don’t like making breaking changes in Eclipse Collections. We are still deciding whether we will make a source breaking change with Verify once we correct the decision to extend Assert . My current thinking is to add all of the Assert methods as deprecated for removal and throw UnsupportedOperationException in all of them with a JavaDoc example and a link to the equivalent method in Assertions to use. When we release the next major release of Eclipse Collections we can then “safely” remove all of the deprecated methods. Phew!

Convenience has a shelf life. Changing inheritance design decisions can be painful and take a long time to correct.

In case you didn’t know

I spoke to a long time Java developer this week who was not aware that static methods can be “inherited” by subclasses. This is an oddity many developers hopefully won’t encounter in their daily development! If you add a static method with the same signature in a subclass, it is referred to as “hiding” not “overriding” the same method in the parent class. If you call static methods on instances of a subclass, the static method that is called will depend on the type of the variable which holds onto the instance. Even if a subclass hides a static method, if the type of an instance of a subtype is set to the superclass type, the static method that is called will be the one defined on the superclass.

If you haven’t heard of or encountered this feature before, the following link might be useful. There are also some blogs out there you can search for to find out how method “hiding” works with static methods on classes.

Thankfully, this static method inheritance feature is not available for static methods on interfaces. This is a very good thing, IMO. It would have made it potentially very messy to add the of() factory methods to List, Set, and Map. The SortedSet interface extends Set, and the SortedMap interface extends Map, but neither interface defines their own of() methods. Imagine if the SortedSet and SortedMap interfaces inherited the of() method from Set and Map. That would be very odd.

Final thoughts

My recommendation is to make your static utility classes final. If you’re going to define a utility class with only static methods, make sure you have a very good reason to not make the class final. My recommendation for users of libraries, don’t extend static utility classes if you are building your own utility classes. You don’t want to turn a Bird into a Statue. Add only the behaviors you need and keep your Statue independent and clean. Just use static utility methods as intended, do not extend them.

Thanks for reading!

I am the creator of and committer for the Eclipse Collections OSS project, which is managed at the Eclipse Foundation. Eclipse Collections is open for contributions.

--

--

Donald Raab
Donald Raab

Written by Donald Raab

Java Champion. Creator of the Eclipse Collections OSS Java library (https://github.com/eclipse/eclipse-collections). Inspired by Smalltalk. Opinions are my own.

No responses yet