BTW, dogs are awesome!

Scala developers tend to use a lot of case classes due to their concise syntax and their role in the functional programming approach. Comparing case class instances is a typical operation, especially in tests. However, it is quite cumbersome, and I will show you that we can do better. Below are assertion messages from the same comparison with:

and without diffx:

In ScalaTest, which is one of the most popular testing frameworks for Scala, we can use the shouldBe matcher to compare case classes. This function, in case of assertion failure, will show us expected and actual objects. Sounds good? Not really. A closer look reveals that under the hood it uses toString method to serialize the case class instance. It causes the following problems:

Case classes which contain multiple levels of nesting are hard to read — they are printed within a single line

There is no way to differentiate between an empty list and a list with an empty string

Assuming that case classes, which are inside of a disordered collection can be uniquely identified by their ids, there is no way to use these ids to pair those case classes during comparison process intelligently

Note: Sometimes I will use term ‘product’ which is just another name for case class

Because of that we decided to create a library which would produce pretty diffs based on the product’s structure. While doing it, we tried to stick to the following assumptions:

Simple to use

Working out of the box with a default configuration which can be easily extended

Collection support

A straightforward way of excluding part of a product from comparison

Let’s discuss each of these points in more details.

Easy to use

Note: Since the core of diffx is only a mechanism for comparison, we will have to use some of the available testing library adapters to see the end-user usage. Right now we only have support for scalatest, but don’t worry, we are working on adding more.

There are two ways we can start using diffx, either by importing DiffMatcher into the scope or extending DiffMatcher trait in our test. First, let us define a simple case class which will be used through our examples:

For scalatest we are defining our own matcher — matchTo which can be used in the following way:

If we now run the test we can enjoy colorful and easy to grasp diffs:

The best thing is that the readability is not decreasing linearly while we compare bigger structures which can be seen on the first example (at the top of this page).

Default configuration which can be easily extended

We have an OOTB support for plain types, product types, coproduct types and collections. If we encounter a custom type the default behavior is to serialize it as a string, but this approach can be easily overrided by specifying an implicit Diff instance for a given type. For example, let us assume we want to compare LocalDateTime instances as dates in ISO format rather than strings.

Collection support

For indexed, collections diffx by default will compare elements which are on the same index. If any elements are missing on either side of the comparison, a proper message from the perspective of the left object will be emitted.

The story is a little bit different when we are dealing with sets or other not indexed collections. Because there is no indexing key to compare items by, we can only compare them on their own without knowing about any relation between them. In case of any differences, this results in reporting objects as missing or additional. Such a message might not be very helpful, but luckily there is a way to alter this behavior.

ObjectMatcher type class purpose is to tell whenever two instances should be considered as the same object from a domain perspective and whereby compared with themselves. E.g. if we would like to treat two instances as the same object, if they share the same value under name property, we would have to specify the following ObjectMatcher :

Having said that, one might also want to compare lists by well-defined domain relation rather than indexes. This will be particularly useful when collections have a different order of elements. To achieve that behavior we need to modify the Diff type class instance for List. Here is how to do it:

The straightforward way of excluding part of a product from comparison

The easiest way to ignore some part of a case class is to specify an identical instance of Diff type class for a given type.

However, this approach has quite a significant downside. We cannot specify the exact path in our structure we would like to ignore. That is because this works based on types so that it will be applied across our whole structure. Also it is not very user-friendly when one has to ignore more than two fields.

The much more powerful way to exclude fields from the comparison is by calling ignore method on a Diff instance. Since Diff instances are immutable, it creates a copy with a modified logic. You can use this instance explicitly. If you still would like to use it implicitly, you will first need to summon the instance of Diff type class using a Derived type class wrapper — Derived[Diff[Person]] . Thanks to that trick you will later be able to put your modified instance of the Diff type class into the implicit scope. The whole process looks as follows:

The ignore method takes a lambda from given type T to ignored field U . It is implemented on top of scala macros, so it is refactoring friendly and type-safe. Ignoring multiple fields is as simple as calling ignore method multiple times. Because ignore method operates on paths rather than types we have a very good precision when specifying ignored fields. In addition to that, we added each macro extension, which can be used from ignore method, to traverse through collections, options and eithers.

If you would like to give it a try check out diffx github repository.