Custom fluent assertions with AssertJ

There is no doubt in modern IT industry that writing tests is an integral part of software development. Untested code is like a black box. We do not know how it will behave in the production environment. And thus leaving a piece of logic without testing puts us in danger of introducing hard to track bugs into the system.

Therefore, almost every day at work each of us writes some tests. It does not matter if they are unit or integration tests — it is just our bread and butter. And yet, more often than not, developers put much less attention to the test code than to the production code. Libraries and frameworks, fluent APIs or just clean code in general are terms we usually connect with actual production code and not with tests. But investing some time to use them in test code can pay off very quickly.

In the article I will show you how we can introduce custom fluent assertions into our code base to make our tests easier to write, read and maintain. This approach might be an overkill for some cases, but I highly recommend to use it for critical parts of the system, such as core domain objects. It helps to avoid code repetition and allows to write a lot of high-quality tests faster.

Domain

Today our goal will be to check invoices generated by the service listed below:

We would like to write a test for InvoiceGenerator and investigate returned Invoice object, making sure that it contains data we expect it to have. The invoice domain object itself is implemented as follows:

The DocumentInfo class is just a simple POJO holding references to invoice number ( String), issuance date and due date ( LocalDates).

Contractor is an abstract class representing either buyer or seller in the invoice:

There are two implementations of Contractor. One for company:

and one for private person:

Lastly there is an InvoiceItem class, which depicts the actual row of invoice with information about product name, quantity, price etc.:

Currency and Unit are just custom enums.

AssertJ

AssertJ is a library that provides fluent assertions interface for Java. It comprises many interesting features like support for Java 8 lambdas, soft assertions or extensive set of assertions for working with collections in a stream like fashion. To get to know all of them I highly recommend to visit its site.

Now let’s take a look how we can use AssertJ’s default API to implement invoice generator test. The easiest to check are simple members of Invoice class:

Investigating buyer and supplier objects is much trickier. Because of the fact that Contractor has two different implementations we need to make additional checks beforehand:

It is quite easy with AssertJ as you see. But still, we need to do instanceof check every time. It would be nice if we could forget about it and about all of the extractions which we need to do in order to ensure that a contractor has the right city value in its address property.

And there are still invoice items to check. If we would only like to make sure that couple of specific products are in the invoice then we can easily make it like this:

But if we would like to delve deeper into InvoiceItem objects then we would need to get them from list, assuming the order in which they appear in it:

And please do not get me wrong — all of the above are perfectly valid and fine code examples. But if the object which we would like to check in our tests constitutes a big part of our domain, then there is a chance that we need to implement things like these quite often. In such a situation introducing our custom assertions into the code base can really pay off in the future.

Custom assertions

The creators of AssertJ have actually thought about writing custom assertions and introduced AbstractAssert class to make it possible for us. The class is generic with first type being self reference and the second one being the type of object we would like to assert. But it is best to explain by an example. So let us start with a simple one regarding the invoice object:

And that’s it — really simple! Our first assertion does not do much for now but it is already very nice to use, especially with proper static import:

This little snippet reads just like natural language. It is the very beginning of a domain specific language for our tests. And what is really fantastic about it is that inside our custom assertion class we still use very handy AssertJ’s default ones. So instead building something completely new we have a possibility to build on to of the features already available.

But we came here with a problem about making assertions regarding Contractor class inside the Invoice. What can be done about it? Well let’s start with introducing custom assertion for an abstract contractor class and solving the problem with reaching deep into the object graph for checking the address property:

This is just a base class from which two specific implementations will inherit. One of them regards private contractor:

The second one (for company) is very similar so I will omit it here.

Unfortunately none of these allow us to forget about contractor instanceof check. We just have two assertion classes now — PrivateContractorAssertion  and CompanyContractorAssertion. To overcome this issue we can introduce one nifty little class letting us forget about it:

With this little improvement now we can write assertions like the one below:

Now the assertion part of our test will be much more readable and concise:

We can implement assertion class for InvoiceItem in the same manner as in the above examples. It does not differ much so it is not presented here.

Nested assertions

We already have couple of nice assertions at hand, but there still remains a problem of retrieving invoice items from list by raw index. It would also be nice if we could have chained all the previous assertions in one call. And this is the time where something I call nested assertions comes in.

The idea for dealing with invoice items checks is pretty simple. If we think about our particular domain it seems quite logical to assume that every InvoiceItem will have unique name in the scope of an Invoice. So instead of getting items by an index we can add method to InvoiceAssertion, which will check if item of particular name is present in the document. If the item is really there then we would return InvoiceItemAssertion from the call. The code looks as follows:

and allows us to write assertion like the one below:

But let’s face it – it does not look very well. Firstly, it does not allow us to chain calls further and check another item in one call. Secondly, the names of the methods do not fit in the flow of reading – there is not much of a fluency here.

Fortunately there is a trick, which will allow us to overcome these problems. We can introduce new assertion class as a wrapper for InvoiceItemAssertion. It will redefine names of the default assertion and provide a method to go up a level and continue chaining our calls. And that is this little hero:

The whole magic here happens because of the fact that NestedInvoiceItemAssertion contains a reference to the parent assertion ( InvoiceAssertion). It allows us to use the and method to get back to the parent assertion and fluently invoke other methods on it (e.g. assert on another nested invoice item). The constructor of  NestedInvoiceItemAssertion  is deliberately made package private so only assertions sitting in the same package could use it. Now there is only one small change to get it going — changing the return type of containsItem method inside InvoiceAssertion code.

If we follow like this and introduce similar utility classes also for previous assertions our final test check could look quite impressive:

Everyone will see instantly what is our intent here cause the code reads just like a book. And what is more, we now have pretty useful tool to reuse in other tests we will write in the future.

In some cases having many assertions in one test is an antipattern. Tests should be focused and follow the Single Responsibility principle. On the other hand, some test cases require complex assertions and it does not mean that the test checks too much. These considerations are generally outside the scope of this article. I’m just showing technical aspects of writing fluent assertion classes which can be reused in many different tests.

Introducing new classes for nested assertions could be debatable. As usual it depends on the actual situation. Let’s take InvoiceItemAssertion  as an example. If we are sure that we would not need it as a standalone assertion in other tests then there is shorter solution. We can just change method names of InvoiceItemAssertion and encompass it with parent field member as in the NestedInvoiceItemAssertion. That would reduce the amount of classes we have to implement.

If, on the other hand, it seems probable that we will need standalone invoice item assertion in the future, then introducing special class just for chained calls seems justified. The example in this article used exact type for parent reference, but it is possible to make the assertion generic on this member. So no matter how deep our nesting will go we would only need one class.

Conclusion

In this post I presented some patterns which allow one to write fluent assertions easily. The described approach allows to write the assertion logic once and then reuse it in many different test scenarios. The resulting assertion code reads almost like a book and makes tests really easy to write.

The obvious drawback of the techniques used in this article is that they require quite a lot of additional code. Because of that it can be an overkill to introduce such assertions for objects which are not likely to need a lot of tests — in such cases pure AssertJ will suffice. But the heart of the system — core domain logic — usually requires many tests (hundreds or more). Writing custom assertions pays off very quickly in such cases — we invest some time at the beginning, but then we can rapidly write many different tests. As always — everything depends on the project specifics and the context.

I hope you will find this article useful. I encourage you to revisit your core domain object test cases — perhaps there is a good spot for writing some custom assertion code?

2 response to "Custom fluent assertions with AssertJ"

  1. By: Artur Wierzbicki Posted: January 21, 2018

    Cześć!

    Też lubię pisać testy w podobnym stylu (w szczególności te dotyczące wielu klas, “modułowe”), z tą różnicą że nie korzystam z żadnego frameworka (tutaj AssertJ) pod spodem.

    Mała sugestia: co byś powiedział na

    zamiast wszystkiego co się wiążę z “and()”? 🙂 Można by też usunąć fluent buildera/metodę .build() w prostszych przypadkach.

    • Mateusz Fedkowicz

      By: Mateusz Fedkowicz Posted: January 23, 2018

      Cześć!

      Dzięki za zainteresowanie artykułem 🙂

      A przechodząc do Twojego pytania – rozumiem, że u Ciebie w przykładzie działa to w ten sposób, że metody asercji (np. isIssuedBy(...)) przyjmują już zbudowane obiekty? Jeśli tak to z pewnością zaletą jest to, że oprócz samej asercji implementujemy tez reużywalne buildery. Natomiast widzę też kilka niedogodności w takim podejściu:

      • Wydaje mi się, że trudno będzie napisać asercję w taki sposób, aby można było pominąć sprawdzanie któregoś z pól (np. chcielibyśmy tylko sprawdzić, czy wystawiający fakturę jest firmą i ma określony identyfikator podatkowy). Możemy wprawdzie polegać na metodzie equals w budowanym obiekcie, ale tą metodę też musimy zaimplementować.
      • Jeśli zdecydujemy się tak jak powyżej opisałem zaimplementować metodę equals to nie do odróżnienia jest sytuacja, w której chcielibyśmy koniecznie sprawdzić, że jakieś pole nie ma ustawionej wartości. Jeśli podamy w builderze null dla jakiejś wartości to w samej asercji nie mamy kontekstu, aby stwierdzić czy chcieliśmy rzeczywiście potwierdzić brak wartości, czy też po prostu nie interesuje nas to pole. W moim podejściu można by dopisać krótką metodę do interesującej nas asercji — przykładowo withoutSurname()

      Podsumowując — jak zwykle wszystko zależy od konkretnego przypadku użycia. Twoje podejście z pewnością sprawdzi się w sytuacjach, kiedy rzeczywiście chcemy sprawdzać wszystkie/większość pól jakichś obiektów i zawsze tak robimy. Zagnieżdżone asercje są bardzo fajne gdy poza kilkoma wyjątkami nie chcemy badać wszystkich właściwości naszych obiektów domenowych.

      PS Przedstawionego przez Ciebie podejścia używam dość często do implementacji builderów dla bardziej skompikowanych obiektów 😉

Leave a Reply

Your email address will not be published. Required fields are marked *