Types of Differs
Here we list the kinds of Differs and how you can use them.
The examples below assume the following imports:
import difflicious._
import difflicious.implicits._
Value Differs
For basic types like Int
, Double
and String
we typically can compare them directly e.g. using equals
method.
If you have a simple type where you don’t need any advanced diffing, then you can use Differ.useEquals
to make a
Differ instance for it.
case class MyInt(i: Int)
object MyInt {
implicit val differ: Differ[MyInt] = Differ.useEquals[MyInt](valueToString = _.toString)
}
MyInt.differ.diff(MyInt(1), MyInt(2))
MyInt(1) -> MyInt(2)
Differs for Algebraic Data Types (enums, sealed traits and case classes)
You can derive Differ
for a case class provided that there is a Differ
instance for all your fields.
Similarly, you can derive a Differ
for a sealed trait (Also called Enums in Scala 3) provided that we’re able to
derive a Differ for subclass of the sealed trait (or a Differ instance is already in scope for that subclass)
Case class
final case class Person(name: String, age: Int)
object Person {
implicit val differ: Differ[Person] = Differ.derived[Person]
}
Person.differ.diff(
Person("Alice", 40),
Person("Alice", 35)
)
Person( name: "Alice", age: 40 -> 35, )
Sealed trait / Scala 3 Enum
// Deriving Differ instance for sealed trait
sealed trait HousePet
final case class Dog(name: String, age: Int) extends HousePet
final case class Cat(name: String, livesLeft: Int) extends HousePet
object HousePet {
implicit val differ: Differ[HousePet] = Differ.derived[HousePet]
}
HousePet.differ.diff(
Dog("Lucky", 1),
Cat("Lucky", 1)
)
Dog != Cat === Obtained === Dog( name: "Lucky", age: 1, ) === Expected === Cat( name: "Lucky", livesLeft: 1, )
Seq Differ
Differ for sequences allow diffing immutable sequences like Seq
, List
, and Vector
.
By default, Seq Differs will match elements by their index in the sequence.
In the example below
- Bob’s age
- Alice isn’t expected to be in list
- Charles is expected but missing
val alice = Person("Alice", 30)
val bob = Person("Bob", 25)
val bob50 = Person("Bob", 50)
val charles = Person("Charles", 80)
Differ.seqDiffer[List, Person].diff(
List(alice, bob50),
List(alice, bob, charles)
)
List( Person( name: "Alice", age: 30, ), Person( name: "Bob", age: 50 -> 25, ), Person( name: "Charles", age: 80, ), )
Pair by field
In many test scenarios we actually don’t care about order of elements, as long as the two sequences contains the same elements. One example of this is inserting multiple records into a database and then retrieving them , where you expect the same records to be returned by not necessarily in the original order.
In this case, you can configure a Differ
to pair by a field instead.
val differByName = Differ[List[Person]].pairBy(_.name)
differByName.diff(
List(bob50, charles, alice),
List(alice, bob, charles)
)
When we match by a person’s name instead of index, we can now easily spot that Bob has the wrong age.
List( Person( name: "Bob", age: 50 -> 25, ), Person( name: "Charles", age: 80, ), Person( name: "Alice", age: 30, ), )
Map differ
Map differ pair entries with the same keys and compare the values. Missing key-values will also be reported in the result.
It requires
- a
ValueDiffer
instance for the map key type (for display purposes) - a
Differ
instance for the map value type
Differ[Map[String, Person]].diff(
Map(
"a" -> alice,
"b" -> bob
),
Map(
"b" -> bob50,
"c" -> charles
),
)
Map( "a" -> Person( name: "Alice", age: 30, ), "b" -> Person( name: "Bob", age: 25 -> 50, ), "c" -> Person( name: "Charles", age: 80, ), )
Set differ
Set differ can diff two Sets by pairing the set elements and diffing them.
By default, the pairing is based on matching elements that are equal to each other (using equals
).
However, you most likely want to pair elements using a field on an element instead for better diffs reports (See next section).
Pair by field
For the best error reporting, you want to configure SetDiffer
to pair by a field.
val differByName: Differ[Set[Person]] = Differ[Set[Person]].pairBy(_.name)
differByName.diff(
Set(bob50, charles, alice),
Set(alice, bob, charles)
)
Set( Person( name: "Bob", age: 50 -> 25, ), Person( name: "Charles", age: 80, ), Person( name: "Alice", age: 30, ), )
Always ignored Differ
Sometimes for certain types you can’t really compare them (e.g. Something that’s not a plain data structure).
In that case you can use Differ.alwaysIgnore
class CantCompare()
val alwaysIgnoredDiffer: Differ[CantCompare] = Differ.alwaysIgnore[CantCompare]