Test-specific Equality versus Domain Equality by Mark Seemann
As a reaction to my two previous blog posts, Joe Miller asks on Twitter:
That's a fair question which I'd like to answer here.
You should definitely strive towards having domain objects with strong equality semantics. Many things (including unit testing) becomes easier when domain objects override Equals in a meaningful manner. By all means should you prefer that over any Resemblance or Likeness craziness.
What is meaningful equality for a domain object? Personally, I find the guidance provided by Domain-Driven Design indispensable:
- If the domain object is an Entity, two objects are equal if their IDs are equal. No other properties should be compared.
- If the domain object is a Value Object, two object are equal if (all) their encapsulated data is equal.
- If the domain object is a Service, by default I'd say that the default reference equality is often the most correct (i.e. don't override Equals).
Now consider the very common case of mapping layers, or the slightly related scenario of an Anti-corruption Layer. In such cases, the code translates Entities to and from other representations. How do we unit test such mapping code?
If we were to rely on the domain object's built-in equality, we would only be testing that the identity of the Entity is as expected, but not whether or not the mapping code properly maps all the other data. In such cases we need Test-specific Equality, and that's exactly what Resemblances and Likenesses provide.
In the previous example, this is exactly what happens. The SUT produces a new instance of the RequestReservationCommand:
[HttpPost] public ViewResult Post(BookingViewModel model) { this.channel.Send(model.MakeReservation()); return this.View("Receipt", model); }
The MakeReservation method is implemented like this:
public RequestReservationCommand MakeReservation() { return new RequestReservationCommand( this.Date, this.Email, this.Name, this.Quantity); }
Notice that nowhere does the code specify the identity of the RequestReservationCommand instance. This is done by the RequestReservationCommand constructor itself, because it's a domain rule that (unless deserialized) each command has a unique ID.
Thus, from the unit test, you have absolutely no chance of knowing what the ID will be (it's a GUID). You could argue back and forth on whether the RequestReservationCommand class is an Entity or a Value Object, but in both cases, Domain Equality would involve comparing IDs, and those will never match. Therefore, the correct Test-specific Equality is to compare the values without the Id property.
Comments
From a behavioral point of view, we don't really care about the value of the ID (the Guid). From other tests we know that a new instance of a command will always have a unique ID. It's part of the command's invariants. Thus, we know that the ID is going to be unique, so having to configure an Ambient Context is only going to add noise to a unit test.