Fakes are Test Doubles with contracts by Mark Seemann
Contracts of Fake Objects can be described by properties.
The first time I tried my hand with the CQRS Booking kata, I abandoned it after 45 minutes because I found that I had little to learn from it. After all, I've already done umpteen variations of (restaurant) booking code examples, in several programming languages. The code example that accompanies my book Code That Fits in Your Head is only the largest and most complete of those.
I also wrote an MSDN Magazine article in 2011 about CQRS, so I think I have that angle covered as well.
Still, while at first glance the kata seemed to have little to offer me, I've found myself coming back to it a few times. It does enable me to focus on something else than the 'production code'. In fact, it turns out that even if (or perhaps particularly when) you use test-driven development (TDD), there's precious little production code. Let's get that out of the way first.
Production code #
The few times I've now done the kata, there's almost no 'production code'. The implied CommandService
has two lines of effective code:
public sealed class CommandService { private readonly IWriteRegistry writeRegistry; private readonly IReadRegistry readRegistry; public CommandService(IWriteRegistry writeRegistry, IReadRegistry readRegistry) { this.writeRegistry = writeRegistry; this.readRegistry = readRegistry; } public void BookARoom(Booking booking) { writeRegistry.Save(booking); readRegistry.RoomBooked(booking); } }
The QueryService
class isn't much more exciting:
public sealed class QueryService { private readonly IReadRegistry readRegistry; public QueryService(IReadRegistry readRegistry) { this.readRegistry = readRegistry; } public static IReadOnlyCollection<Room> Reserve( Booking booking, IReadOnlyCollection<Room> existingView) { return existingView.Where(r => r.Name != booking.RoomName).ToList(); } public IReadOnlyCollection<Room> GetFreeRooms(DateOnly arrival, DateOnly departure) { return readRegistry.GetFreeRooms(arrival, departure); } }
The kata only suggests the GetFreeRooms
method, which is only a single line. The only reason the Reserve
function also exists is to pull a bit of testable logic back from the below Fake object. I'll return to that shortly.
I've also done the exercise in F#, essentially porting the C# implementation, which only highlights how simple it all is:
module CommandService = let bookARoom (writeRegistry : IWriteRegistry) (readRegistry : IReadRegistry) booking = writeRegistry.Save booking readRegistry.RoomBooked booking module QueryService = let reserve booking existingView = existingView |> Seq.filter (fun r -> r.Name <> booking.RoomName) let getFreeRooms (readRegistry : IReadRegistry) arrival departure = readRegistry.GetFreeRooms arrival departure
That's both the Command side and the Query side!
This represents my honest interpretation of the kata. Really, there's nothing to it.
The reason I still find the exercise interesting is that it explores other aspects of TDD than most katas. The most common katas require you to write a little algorithm: Bowling, Word wrap, Roman Numerals, Diamond, Tennis, etc.
The CQRS Booking kata suggests no interesting algorithm, but rather teaches some important lessons about software architecture, separation of concerns, and, if you approach it with TDD, real-world test automation. In contrast to all those algorithmic exercises, this one strongly suggests the use of Test Doubles.
Fakes #
You could attempt the kata with a dynamic 'mocking' library such as Moq or Mockito, but I haven't tried. Since Stubs and Mocks break encapsulation I favour Fake Objects instead.
Creating a Fake write registry is trivial:
internal sealed class FakeWriteRegistry : Collection<Booking>, IWriteRegistry { public void Save(Booking booking) { Add(booking); } }
Its counterpart, the Fake read registry, turns out to be much more involved:
internal sealed class FakeReadRegistry : IReadRegistry { private readonly IReadOnlyCollection<Room> rooms; private readonly IDictionary<DateOnly, IReadOnlyCollection<Room>> views; public FakeReadRegistry(params Room[] rooms) { this.rooms = rooms; views = new Dictionary<DateOnly, IReadOnlyCollection<Room>>(); } public IReadOnlyCollection<Room> GetFreeRooms(DateOnly arrival, DateOnly departure) { return EnumerateDates(arrival, departure) .Select(GetView) .Aggregate(rooms.AsEnumerable(), Enumerable.Intersect) .ToList(); } public void RoomBooked(Booking booking) { foreach (var d in EnumerateDates(booking.Arrival, booking.Departure)) { var view = GetView(d); var newView = QueryService.Reserve(booking, view); views[d] = newView; } } private static IEnumerable<DateOnly> EnumerateDates(DateOnly arrival, DateOnly departure) { var d = arrival; while (d < departure) { yield return d; d = d.AddDays(1); } } private IReadOnlyCollection<Room> GetView(DateOnly date) { if (views.TryGetValue(date, out var view)) return view; else return rooms; } }
I think I can predict the most common reaction: That's much more code than the System Under Test! Indeed. For this particular exercise, this may indicate that a 'dynamic mock' library may have been a better choice. I do, however, also think that it's an artefact of the kata description's lack of requirements.
As is evident from the restaurant sample code that accompanies Code That Fits in Your Head, once you add realistic business rules the production code grows, and the ratio of test code to production code becomes better balanced.
The size of the FakeReadRegistry
class also stems from the way the .NET base class library API is designed. The GetView
helper method demonstrates that it requires four lines of code to look up an entry in a dictionary but return a default value if the entry isn't found. That's a one-liner in F#:
let getView (date : DateOnly) = views |> Map.tryFind date |> Option.defaultValue rooms |> Set.ofSeq
I'll show the entire F# Fake later, but you could also play some CC golf with the C# code. That's a bit besides the point, though.
Command service design #
Why does FakeReadRegistry
look like it does? It's a combination of the kata description and my prior experience with CQRS. When adopting an asynchronous message-based architecture, I would usually not implement the write side exactly like that. Notice how the CommandService
class' BookARoom
method seems to repeat itself:
public void BookARoom(Booking booking) { writeRegistry.Save(booking); readRegistry.RoomBooked(booking); }
While semantically it seems to be making two different statements, structurally they're identical. If you rename the methods, you could wrap both method calls in a single Composite. In a more typical CQRS architecture, you'd post a Command on bus:
public void BookARoom(Booking booking) { bus.BookRoom(booking); }
This makes that particular BookARoom
method, and perhaps the entire CommandService
class, look redundant. Why do we need it?
As presented here, we don't, but in a real application, the Command service would likely perform some pre- and post-processing. For example, if this was a web application, the Command service might instead be a Controller concerned with validating and translating HTTP- or Web-based input to a Domain Object before posting to the bus.
A realistic code base would also be asynchronous, which, on .NET, would imply the use of the async
and await
keywords, etc.
Read registry design #
A central point of CQRS is that you can optimise the read side for the specific tasks that it needs to perform. Instead of performing a dynamic query every time a client requests a view, you can update and persist a view. Imagine having a JSON or HTML file that the system can serve upon request.
Part of handling a Command or Event is that the system background processes update persistent views once per event.
For the particular hotel booking system, I imagine that the read registry has a set of files, blobs, documents, or denormalised database rows. When it receives notification of a booking, it'll need to remove that room from the dates of the booking.
While a booking may stretch over several days, I found it simplest to think of the storage system as subdivided into single dates, instead of ranges. Indeed, the GetFreeRooms
method is a ranged query, so if you really wanted to denormalise the views, you could create a persistent view per range. This would, however, require that you precalculate and persist a view for October 2 to October 4, and another one for October 2 to October 5, and so on. The combinatorial explosion suggests that this isn't a good idea, so instead I imagine keeping a persistent view per date, and then perform a bit of on-the-fly calculation per query.
That's what FakeReadRegistry
does. It also falls back to a default collection of rooms
for all the dates that are yet untouched by a booking. This is, again, because I imagine that I might implement a real system like that.
You may still protest that the FakeReadRegistry
duplicates production code. True, perhaps, but if this really is a concern, you could refactor it to the Template Method pattern.
Still, it's not really that complicated; it only looks that way because C# and the Dictionary API is too heavy on ceremony. The Fake looks much simpler in F#:
type FakeReadRegistry (rooms : IReadOnlyCollection<Room>) = let mutable views = Map.empty let enumerateDates (arrival : DateOnly) departure = Seq.initInfinite id |> Seq.map arrival.AddDays |> Seq.takeWhile (fun d -> d < departure) let getView (date : DateOnly) = views |> Map.tryFind date |> Option.defaultValue rooms |> Set.ofSeq interface IReadRegistry with member this.GetFreeRooms arrival departure = enumerateDates arrival departure |> Seq.map getView |> Seq.fold Set.intersect (Set.ofSeq rooms) |> Set.toList :> _ member this.RoomBooked booking = for d in enumerateDates booking.Arrival booking.Departure do let newView = getView d |> QueryService.reserve booking |> Seq.toList views <- Map.add d newView views
This isn't just more dense than the corresponding C# code, as F# tends to be, it also has a lower cyclomatic complexity. Both the EnumerateDates
and GetView
C# methods have a cyclomatic complexity of 2, while their F# counterparts rate only 1.
For production code, cyclomatic complexity of 2 is fine if the code is covered by automatic tests. In test code, however, we should be wary of any branching or looping, since there are (typically) no tests of the test code.
While I am going to show some tests of that code in what follows, I do that for a different reason.
Contract #
When explaining Fake Objects to people, I've begun to use a particular phrase:
A Fake Object is a polymorphic implementation of a dependency that fulfils the contract, but lacks some of the ilities.
It's funny how you can arrive at something that strikes you as profound, only to discover that it was part of the definition all along:
"We acquire or build a very lightweight implementation of the same functionality as provided by a component on which the SUT [System Under Test] depends and instruct the SUT to use it instead of the real DOC [Depended-On Component]. This implementation need not have any of the "-ilities" that the real DOC needs to have"
A common example is a Fake Repository object that pretends to be a database, often by leveraging a built-in collection API. The above FakeWriteRegistry
is as simple an example as you could have. A slightly more compelling example is the FakeUserRepository shown in another article. Such an 'in-memory database' fulfils the implied contract, because if you 'save' something in the 'database' you can later retrieve it again with a query. As long as the object remains in memory.
The ilities that such a Fake database lacks are
- data persistence
- thread safety
- transaction support
and perhaps others. Such qualities are clearly required in a real production environment, but are in the way in an automated testing context. The implied contract, however, is satisfied: What you save you can later retrieve.
Now consider the IReadRegistry
interface:
public interface IReadRegistry { IReadOnlyCollection<Room> GetFreeRooms(DateOnly arrival, DateOnly departure); void RoomBooked(Booking booking); }
Which contract does it imply, given what you know about the CQRS Booking kata?
I would suggest the following:
- Precondition:
arrival
should be less than (or equal?) todeparture
. - Postcondition:
GetFreeRooms
should always return a result. Null isn't a valid return value. - Invariant: After calling
RoomBooked
,GetFreeRooms
should exclude that room when queried on overlapping dates.
There may be other parts of the contract than this, but I find the third one most interesting. This is exactly what you would expect from a real system: If you reserve a room, you'd be surprised to see GetFreeRooms
indicating that this room is free if queried about dates that overlap the reservation.
This is the sort of implied interaction that Stubs and Mocks break, but that FakeReadRegistry
guarantees.
Properties #
There's a close relationship between contracts and properties. Once you can list preconditions, invariants, and postconditions for an object, there's a good chance that you can write code that exercises those qualities. Indeed, why not use property-based testing to do so?
I don't wish to imply that you should (normally) write tests of your test code. The following rather serves as a concretisation of the notion that a Fake Object is a Test Double that implements the 'proper' behaviour. In the following, I'll subject the FakeReadRegistry
class to that exercise. To do that, I'll use CsCheck 2.14.1 with xUnit.net 2.5.3.
Before tackling the above invariant, there's a simpler invariant specific to the FakeReadRegistry
class. A FakeReadRegistry
object takes a collection of rooms
via its constructor, so for this particular implementation, we may wish to establish the reasonable invariant that GetFreeRooms
doesn't 'invent' rooms on its own:
private static Gen<Room> GenRoom => from name in Gen.String select new Room(name); [Fact] public void GetFreeRooms() { (from rooms in GenRoom.ArrayUnique from arrival in Gen.Date.Select(DateOnly.FromDateTime) from i in Gen.Int[1, 1_000] let departure = arrival.AddDays(i) select (rooms, arrival, departure)) .Sample((rooms, arrival, departure) => { var sut = new FakeReadRegistry(rooms); var actual = sut.GetFreeRooms(arrival, departure); Assert.Subset(new HashSet<Room>(rooms), new HashSet<Room>(actual)); }); }
This property asserts that the actual
value returned from GetFreeRooms
is a subset of the rooms
used to initialise the sut
. Recall that the subset relation is reflexive; i.e. a set is a subset of itself.
The same property written in F# with Hedgehog 0.13.0 and Unquote 6.1.0 may look like this:
module Gen = let room = Gen.alphaNum |> Gen.array (Range.linear 1 10) |> Gen.map (fun chars -> { Name = String chars }) let dateOnly = let min = DateOnly(2000, 1, 1).DayNumber let max = DateOnly(2100, 1, 1).DayNumber Range.linear min max |> Gen.int32 |> Gen.map DateOnly.FromDayNumber [<Fact>] let GetFreeRooms () = Property.check <| property { let! rooms = Gen.room |> Gen.list (Range.linear 0 100) let! arrival = Gen.dateOnly let! i = Gen.int32 (Range.linear 1 1_000) let departure = arrival.AddDays i let sut = FakeReadRegistry rooms :> IReadRegistry let actual = sut.GetFreeRooms arrival departure test <@ Set.isSubset (Set.ofSeq rooms) (Set.ofSeq actual) @> }
Simpler syntax, same idea.
Likewise, we can express the contract that describes the relationship between RoomBooked
and GetFreeRooms
like this:
[Fact] public void RoomBooked() { (from rooms in GenRoom.ArrayUnique.Nonempty from arrival in Gen.Date.Select(DateOnly.FromDateTime) from i in Gen.Int[1, 1_000] let departure = arrival.AddDays(i) from room in Gen.OneOfConst(rooms) from id in Gen.Guid let booking = new Booking(id, room.Name, arrival, departure) select (rooms, booking)) .Sample((rooms, booking) => { var sut = new FakeReadRegistry(rooms); sut.RoomBooked(booking); var actual = sut.GetFreeRooms(booking.Arrival, booking.Departure); Assert.DoesNotContain(booking.RoomName, actual.Select(r => r.Name)); }); }
or, in F#:
[<Fact>] let RoomBooked () = Property.check <| property { let! rooms = Gen.room |> Gen.list (Range.linear 1 100) let! arrival = Gen.dateOnly let! i = Gen.int32 (Range.linear 1 1_000) let departure = arrival.AddDays i let! room = Gen.item rooms let! id = Gen.guid let booking = { ClientId = id RoomName = room.Name Arrival = arrival Departure = departure } let sut = FakeReadRegistry rooms :> IReadRegistry sut.RoomBooked booking let actual = sut.GetFreeRooms arrival departure test <@ not (Seq.contains room actual) @> }
In both cases, the property books a room and then proceeds to query GetFreeRooms
to see which rooms are free. Since the query is exactly in the range from booking.Arrival
to booking.Departure
, we expect not to see the name of the booked room among the free rooms.
(As I'm writing this, I think that there may be a subtle bug in the F# property. Can you spot it?)
Conclusion #
A Fake Object isn't like other Test Doubles. While Stubs and Mocks break encapsulation, a Fake Object not only stays encapsulated, but it also fulfils the contract implied by a polymorphic API (interface or base class).
Or, put another way: When is a Fake Object the right Test Double? When you can describe the contract of the dependency.
But if you can't describe the contract of a dependency, you should seriously consider if the design is right.