Part 3: TDD in Object-Oriented World
So far, we’ve talked a lot about the object-oriented world, consisting of objects that exhibit the following properties:
- Objects send messages to each other using interfaces and according to protocols. As long as these interfaces and protocols are adhered to by the recipients of the messages, the sender objects don’t need to know who exactly is on the other side to handle the message. In other words, interfaces and protocols allow decoupling senders from the identity of their recipients.
- Objects are built with Tell Don’t Ask heuristic in mind, so that each object has its own responsibility and fulfills it when it’s told to do something, without revealing the details of how it handles this responsibility,
- Objects, their interfaces and protocols, are designed with composability in mind, which allows us to compose them as we would compose parts of sentences, creating small higher-level languages, so that we can reuse the objects we already have as our “vocabulary” and add more functionality by combining them into new “sentences”.
- Objects are created in places well separated from the places that use those objects. The place of object creation depends on the object lifecycle - it may be e.g. a factory or a composition root.
The world of objects is complemented by the world of values that exhibit the following characteristics:
- Values represent quantities, measurements and other discrete pieces of data that we want to name, combine, transform and pass along. Examples are dates, strings, money, time durations, path values, numbers, etc.
- Values are compared based on their data, not their references. Two values containing the same data are considered equal.
- Values are immutable - when we want to have a value like another one, but with one aspect changed, we create a new value containing this change based on the previous value and the previous value remains unchanged.
- Values do not (typically) rely on polymorphism - if we have several value types that need to be used interchangeably, the usual strategy is to provide explicit conversion methods between those types.
There are times when choosing whether something should be an object or a value poses a problem (I ran into situations when I modeled the same concept as a value in one application and as an object in another), so there is no strict rule on how to choose and, additionally, different people have different preferences.
This joint world is the world we are going to fit mock objects and other TDD practices into in the next part.
I know we have put TDD aside for such a long time. Believe me that this is because I consider understanding the concepts from part 2 crucial to getting mocks right.
Mock objects are not a new tool, however, there is still a lot of misunderstanding of what their nature is and where and how they fit best into the TDD approach. Some opinions went as far as to say that there are two styles of TDD: one that uses mocks (called “mockist TDD” or “London style TDD”) and another without them (called “classic TDD” or “Chicago style TDD”). I don’t support this division. I like very much what Nat Pryce wrote about it:
(…) I argue that there are not different kinds of TDD. There are different design conventions, and you pick the testing techniques and tools most appropriate for the conventions you’re working in.
The explanation of the “design conventions” that mocks were born from required putting you through so many pages about a specific view on object-oriented design. This is the view that mock objects as a tool and as a technique were chosen to support. Talking about mock objects out of the context of this view would make me feel like I’m painting a false picture.
After reading part 3, you will understand how mocks fit into test-driving object-oriented code, how to make Statements using mocks maintainable and how some of the practices I introduced in the chapters of part 1 apply to mocks. You will also be able to test-drive simple object-oriented systems.
Mock Objects as a testing tool
Remember one of the first chapters of this book, where I introduced mock objects and mentioned that I had lied to you about their true purpose and nature? Now that we have a lot more knowledge of object-oriented design (at least on a specific, opinionated view on it), we can truly understand where mocks come from and what they are for.
In this chapter, I won’t say anything about the role of mock objects in test-driving object-oriented code yet. For now, I want to focus on justifying their place in the context of testing objects written in the style that I described in part 2.
A backing example
For the need of this chapter, I will use one toy example. Before I describe it, I need you to know that I don’t consider this example a showcase for mock objects. Mocks shine where there are domain-driven interactions between objects and this example is not like that - the interactions here are more implementation-driven. Still, I decided to use it anyway because I consider it something easy to understand and good enough to discuss some mechanics of mock objects. In the next chapter, I will use the same example as an illustration, but after that, I’m dropping it and going into more interesting stuff.
The example is a single class, called DataDispatch, which is responsible for sending received data to a channel (represented by a Channel interface). The Channel needs to be opened before the data is sent and closed after. DataDispatch implements this requirement. Here is the full code for the DataDispatch class:
1 public class DataDispatch
2 {
3 private Channel _channel;
4
5 public DataDispatch(Channel channel)
6 {
7 _channel = channel;
8 }
9
10 public void Dispatch(byte[] data)
11 {
12 _channel.Open();
13 try
14 {
15 _channel.Send(data);
16 }
17 finally
18 {
19 _channel.Close();
20 }
21 }
22 }
The rest of this chapter will focus on dissecting the behaviors of DataDispatch and their context.
I will start describing this context by looking at the interface used by DataDispatch.
Interfaces
As shown above, DataDispatch depends on a single interface called Channel. Here is the full definition of this interface:
1 public interface Channel
2 {
3 void Open();
4 void Send(byte[] data);
5 void Close();
6 }
An implementation of Channel is passed into the constructor of DataDispatch. In other words, DataDispatch can be composed with anything that implements Channel interface. At least from the compiler’s point of view. This is because, as I mentioned in the last part, for two composed objects to be able to work together successfully, interfaces are not enough. They also have to establish and follow a protocol.
Protocols
Note that when we look at the DataDispatch class, there are two protocols it has to follow. I will describe them one by one.
Protocol between DataDispatch and its user
The first protocol is between DataDispatch and the code that uses it, i.e. the one that calls the Dispatch() method. Someone, somewhere, has to do the following:
1 dataDispatch.Send(messageInBytes);
or there would be no reason for DataDispatch to exist. Looking further into this protocol, we can note that DataDispatch does not require too much from its users – it doesn’t have any kind of return value. The only feedback it gives to the code that uses it is rethrowing any exception raised by a channel, so the user code must be prepared to handle the exception. Note that DataDispatch neither knows nor defines the kinds of exceptions that can be thrown. This is the responsibility of a particular channel implementation. The same goes for deciding under which condition should an exception be thrown.
Protocol between DataDispatch and Channel
The second protocol is between DataDispatch and Channel. Here, DataDispatch will work with any implementation of Channel the allows it to invoke the methods of a Channel specified number of times in a specified order:
- Open the channel – once,
- Send the data – once,
- Close the channel – once.
Whatever actual implementation of Channel interface is passed to DataDispatch, it will operate on the assumption that this indeed is the count and order in which the methods will be called. Also, DataDispatch assumes it is required to close the channel in case of error while sending data (hence the finally block wrapping the Close() method invocation).
Two conversations
Summing it up, there are two “conversations” a DataDispatch object is involved in when fulfilling its responsibilities – one with its user and one with a dependency passed by its creator. We cannot specify these two conversations separately as the outcome of each of these two conversations depends on the other. Thus, we have to specify the DataDispatch class, as it is involved in both of these conversations at the same time.
Roles
Our conclusion from the last section is that the environment in which behaviors of DataDispatch take place is comprised of three roles (arrows show the direction of dependencies, or “who sends messages to whom”):
1 User -> DataDispatch -> Channel
Where DataDispatch is the specified class and the rest is its context (Channel being the part of the context DataDispatch depends on. As much as I adore context-independence, most classes need to depend on some kind of context, even if to a minimal degree).
Let’s use this environment to define the behaviors of DataDispatch we need to specify.
Behaviors
The behaviors of DataDispatch defined in terms of this context are:
- Dispatching valid data:
1 GIVEN User wants to dispatch a piece of data
2 AND a DataDispatch instance is connected to a Channel
3 that accepts such data
4 WHEN the User dispatches the data via the DataDispatch instance
5 THEN the DataDispatch object should
6 open the channel,
7 then send the User data through the channel,
8 then close the channel
- Dispatching invalid data:
1 GIVEN User wants to dispatch a piece of data
2 AND a DataDispatch instance is connected to a Channel
3 that rejects such data
4 WHEN the User dispatches the data via the DataDispatch instance
5 THEN the DataDispatch object should report to the User
6 that data is invalid
7 AND close the connection anyway
For the remainder of this chapter, I will focus on the first behavior as our goal for now, is not to create a complete Specification of DataDispatch class, but rather to observe some mechanics of mock objects as a testing tool.
Filling in the roles
As mentioned before, the environment in which the behavior takes place looks like this:
1 User -> DataDispatch -> Channel
Now we need to say who will play these roles. I marked the ones we don’t have filled yet with question marks (?):
1 User? -> DataDispatch? -> Channel?
Let’s start with the role of DataDispatch. Probably not surprisingly, it will be filled by the concrete class DataDispatch – after all, this is the class that we specify.
Our environment looks like this now:
1 User? -> DataDispatch (concrete class) -> Channel?
Next, who is going to be the user of the DataDispatch class? For this question, I have an easy answer – the Statement body is going to be the user – it will interact with DataDispatch to trigger the specified behaviors. This means that our environment looks like this now:
1 Statement body -> DataDispatch (concrete class) -> Channel?
Now, the last element is to decide who is going to play the role of a channel. We can express this problem with the following, unfinished Statement (I marked all the current unknowns with a double question mark: ??):
1 [Fact] public void
2 ShouldSendDataThroughOpenChannelThenCloseWhenAskedToDispatch()
3 {
4 //GIVEN
5 Channel channel = ??; //what is it going to be?
6 var dispatch = new DataDispatch(channel);
7 var data = Any.Array<byte>();
8
9 //WHEN
10 dispatch.ApplyTo(data);
11
12 //THEN
13 ?? //how to specify DataDispatch behavior?
14 }
As you see, we need to pass an implementation of Channel to a DataDispatch, but we don’t know what this channel should be. Likewise, we have no good idea of how to specify the expected calls and their order.
From the perspective of DataDispatch, it is designed to work with everything that implements the Channel interface and follows the protocol, so there is no single “privileged” implementation that is more appropriate than others. This means that we can pretty much pick and choose the one we like best. Which one do we like best? The one that makes writing the specification easiest, of course. Ideally, we’d like to pass a channel that best fulfills the following requirements:
- Adds as little side effects of its own as possible. If a channel implementation used in a Statement added side effects, we would never be sure whether the behavior we observe when executing our Specification is the behavior of
DataDispatchor maybe the behavior of the particularChannelimplementation that is used in this Statement. This is a requirement of trust – we want to trust our Specification that it specifies what it says it does. - Is easy to control – so that we can easily make it trigger different conditions in the object we are specifying. Also, we want to be able to easily verify how the specified object interacts with it. This is a requirement of convenience.
- Is quick to create and easy to maintain – because we want to focus on the behaviors we specify, not on maintaining or creating helper classes. This is a requirement of low friction.
There is a tool that fulfills these three requirements better than others I know of and it’s called a mock object. Here’s how it fulfills the mentioned requirements:
- Mocks add almost no side effects of its own. Although they do have some hardcoded default behaviors (e.g. when a method returning
intis called on a mock, it returns0by default), but these behaviors are as default and meaningless as they can possibly be. This allows us to put more trust in our Specification. - Mocks are easy to control - every mocking library comes provided with an API for defining pre-canned method call results and for verification of received calls. Having such API provides convenience, at least from my point of view.
- Mocks can be trivial to maintain. While you can write your own mocks (i.e. your own implementation of an interface that allows setting up and verifying calls), most of us use libraries that generate them, typically using a reflection feature of a programming language (in our case, C#). Typically, mock libraries free us from having to maintain mock implementations, lowering the friction of writing and maintaining our executable Statements.
So let’s use a mock in place of Channel! This makes our environment of the specified behavior look like this:
1 Statement body -> DataDispatch (concrete class) -> Mock Channel
Note that the only part of this environment that comes from production code is the DataDispatch, while its context is Statement-specific.
Using a mock channel
I hope you remember the NSubstitute library for creating mock objects that I introduced way back at the beginning of the book. We can use it now to quickly create an implementation of Channel that behaves the way we like, allows easy verification of protocol and between Dispatch and Channel and introduces the minimal number of side effects.
By using this mock to fill the gaps in our Statement, this is what we end up with:
1 [Fact] public void
2 ShouldSendDataThroughOpenChannelThenCloseWhenAskedToDispatch()
3 {
4 //GIVEN
5 var channel = Substitute.For<Channel>();
6 var dispatch = new DataDispatch(channel);
7 var data = Any.Array<byte>();
8
9 //WHEN
10 dispatch.ApplyTo(data);
11
12 //THEN
13 Received.InOrder(() =>
14 {
15 channel.Open();
16 channel.Send(data);
17 channel.Close();
18 };
19 }
previously, this Statement was incomplete, because we lacked the answer to the following two questions:
- Where to get the channel from?
- How to verify
DataDispatchbehavior?
I answered the question of “where to get the channel from?” by creating it as a mock:
1 var channel = Substitute.For<Channel>();
Then the second question: “how to verify DataDispatch behavior?” was answered by using the NSubstitute API for verifying that the mock received three calls (or three messages) in a specific order:
1 Received.InOrder(() =>
2 {
3 channel.Open();
4 channel.Send(data);
5 channel.Close();
6 };
The consequence is that if I rearrange the order of the messages sent to Channel in the implementation of the ApplyTo() method from this one:
1 public void Dispatch(byte[] data)
2 {
3 _channel.Open();
4 _channel.Send(data);
5 _channel.Close();
6 }
to this one (note the changed call order):
1 public void Dispatch(byte[] data)
2 {
3 _channel.Send(data); //before Open()!
4 _channel.Open();
5 _channel.Close();
6 }
The Statement will turn false (i.e. will fail).
Mocks as yet another context
What we did in the above example was to put our DataDispatch in a context that was most trustworthy, convenient and frictionless for us to use in our Statement.
Some say that specifying object interactions in the context of mocks is “specifying in isolation” and that providing such mock dependencies is “isolating” the class from its “real” dependencies. I don’t identify with this point of view very much. From the point of view of a specified class, mocks are yet another context – they are neither better, nor worse, they are neither more nor less real than other contexts we want to put our Dispatch in. Sure, this is not the context in which it runs in production, but we may have other situations than mere production work – e.g. we may have a special context for demos, where we count sent packets and show the throughput on a GUI screen. We may also have a debugging context that in each method, before passing the control to production code, writes a trace message to a log. The DataDispatch class may be used in the production code in several contexts at the same time. We may dispatch data through the network, to a database and a file all at the same time in our application and the DataDispatch class may be used in all these scenarios, each time connected to a different implementation of Channel and used by a different piece of code.
Summary
The goal of this chapter was only to show you how mock objects fit into testing code written in a “tell don’t ask” style, focusing on roles, responsibilities, behaviors, interfaces, and protocols of objects. This example was meant as something you could easily understand, not as a showcase for TDD using mocks. For one more chapter, we will work on this toy example and then I will try to show you how I apply mock objects in more interesting cases.
Test-first using mock objects
Now that we saw mocks in action and placed them in the context of a specific design approach, I’d like to show you how mock objects are used when employing the test-first approach. To do that, I’m going to reiterate the example from the last chapter. I already mentioned how this example is not particularly strong in terms of showcasing the power of mock objects, so I won’t repeat myself here. In the next chapter, I will give you an example I consider more suited.
How to start? – with mock objects
You probably remember the chapter “How to start?” from part 1 of this book. In that chapter, I described the following ways to kick-start writing a Statement before the actual implementation is in place:
- Start with a good name.
- Start by filling the GIVEN-WHEN-THEN structure with the obvious.
- Start from the end.
- Start by invoking a method if you have one.
Pretty much all of these strategies work equally well with Statements that use mock objects, so I’m not going to describe them in detail again. In this chapter, I will focus on one of the strategies: “Start by invoking a method if you have one” as it’s the one I use most often. This is driven not only by my choice to use mock objects, but also by the development style I tend to use. This style is called “outside-in” and all we need to know about it, for now, is that following it means starting the development from the inputs of the system and ending on the outputs. Some may consider it counter-intuitive as it means we will write classes collaborating with classes that don’t exist yet. I will give you a small taste of it (together with a technique called “interface discovery”) in this chapter and will expand on these ideas in the next one.
Responsibility and Responsibility
In this chapter, I will be using two concepts that, unfortunately, happen to share the same name: “responsibility”. One meaning of responsibility was coined by Rebecca Wirfs-Brock to mean “an obligation to perform a task or know certain information”, and the other by Robert C. Martin to mean “a reason to change”. To avoid this ambiguity, I will try calling the first one “obligation” and the second one “purpose” in this chapter.
The relationship between the two can be described by the following sentences:
- A class has obligations towards its clients.
- The obligations are what the class “promises” to do for its clients.
- The class does not have to fulfill the obligations alone. Typically, it does so with help from other objects – its collaborators. Those collaborators, in turn, have their obligations and collaborators.
- Each of the collaborators has its purpose - a role in fulfilling the main obligation. The purpose results from the decomposition of the main obligation.
Channel and DataDispatch one more time
Remember the example from the last chapter? Imagine we are in a situation where we already have the DataDispatch class, but its implementation is empty – after all, this is what we’re going to test-drive.
So for now, the DataDispatch class looks like this
1 public class DataDispatch
2 {
3 public void ApplyTo(byte[] data)
4 {
5 throw new NotImplementedException();
6 }
7 }
Where did I get this class from in this shape? Well, let’s assume for now that I am in the middle of development and this class is a result of my earlier TDD activities (after reading this and the next chapter, you’ll hopefully have a better feel on how it happens).
The first behavior
A TDD cycle starts with a false Statement. What behavior should it describe? I’m not sure yet, but, as I already know the class that will have the behaviors that I want to specify, plus it only has a single method (ApplyTo()), I can almost blindly write a Statement where I create an object of this class and invoke the method:
1 [Fact] public void
2 ShouldXXXXXXXXXYYY() //TODO give a better name
3 {
4 //GIVEN
5 var dispatch = new DataDispatch();
6
7 //WHEN
8 dispatch.ApplyTo(); //TODO doesn't compile
9
10 //THEN
11 Assert.True(false); //TODO state expectations
12 }
Note several things:
- I’m now using a placeholder name for the Statement and I added a TODO item to my list to correct it later when I define the purpose and behavior of
DataDispatch. - According to its signature, the
ApplyTo()method takes an argument, but I didn’t provide any in the Statement. For now, I don’t want to think too hard, I just want to brain-dump everything I know. - the
//THENsection is empty for now – it only has a single assertion that is designed to fail when the execution flow reaches it (this way I protect myself from mistakenly making the Statement true until I state my real expectations). I will define the//THENsection once I figure out what is the purpose that I want to give this class and the behavior that I want to specify. - If you remember the
Channelinterface from the last chapter, then imagine that it doesn’t exist yet and that I don’t even know that I will need it. I will “discover” it later.
Leaning on the compiler
So I did my brain dump. What do I do now? I don’t want to think too hard yet (time will come for that). First, I reach for the feedback to my compiler – maybe it can give me some hints on what I am missing?
Currently, the compiler complains that I invoke the ApplyTo() method without passing any argument. What’s the name of the argument? As I look up the signature of the ApplyTo() method, it looks like the name is data:
1 public void ApplyTo(byte[] data)
Hmm, if it’s data it wants, then let’s pass some data. I don’t want to decide what it is yet, so I will act as if I had a variable called data and just write its name where the argument is expected:
1 [Fact] public void
2 ShouldXXXXXXXXXYYY() //TODO give a better name
3 {
4 //GIVEN
5 var dispatch = new DataDispatch();
6
7 //WHEN
8 dispatch.ApplyTo(data); //TODO still doesn't compile
9
10 //THEN
11 Assert.True(false); //TODO state expectations
12 }
The compiler gives me more feedback – it says my data variable is undefined. It might sound funny (as if I didn’t know!), but this way I come one step further. Now I know I need to define this data. I can use a “quick fix” capability of my IDE to introduce a variable. E.g. in Jetbrains IDEs (IntelliJ IDEA, Resharper, Rider…) this can be done by pressing ALT + ENTER when the cursor is on the name of the missing variable. The IDE will create the following declaration:
1 byte[] data;
Note that the IDE guessed the type of the variable for me. How did it know? Because the definition of the method where I try to pass it already has the type declared:
1 public void ApplyTo(byte[] data)
Of course, the declaration of data that my IDE put in the code will still not compile because C# requires variables to be explicitly initialized. So the code should look like this:
1 byte[] data = ... /* whatever initialization code*/;
Turning the brain on – what about data?
It looks like I can’t continue my brain-dead parade anymore. To decide how to define this data, I have to turn my thought processes on and decide what exactly is the obligation of the ApplyTo() method and what does it need the data for. After some thinking, I decide that applying data dispatch should send the data it receives. But… should it do all the work, or maybe delegate some parts? There are at least two subtasks associated with sending the data:
- The raw sending logic (laying out the data, pushing it e.g. through a web socket, etc.)
- Managing the connection lifetime (deciding when it should be opened and when closed, disposing of all the allocated resources, even in the face of an exception that may occur while sending).
I decide against putting the entire logic in the DataDispatch class, because:
- It would have more than one purpose (as described earlier) – in other words, it would violate the Single Responsibility Principle.
- I am mentally unable to figure out how to write a false Statement for so much logic before the implementation. I always treat it as a sign that I’m trying to put too much of a burden on a single class1.
Introducing a collaborator
Thus, I decide to divide and conquer, i.e. find DataDispatch some collaborators that will help it achieve its goal and delegate parts of the logic to them. After some consideration, I conclude that the purpose of DataDispatch should be managing the connection lifetime. The rest of the logic I decide to delegate to a collaborator role I call a Channel. The process of coming up with collaborator roles and delegating parts of specified class obligations to them is called interface discovery.
Anyway, since my DataDispatch is going to delegate some logic to the Channel, it has to know it. Thus, I’ll connect this new collaborator to the DataDispatch. A DataDispatch will not work without a Channel, which means I need to pass the channel to DataDispatch as a constructor parameter. It’s tempting me to forget TDD, just go to the implementation of this constructor and add a parameter there, but I resist. As usual, I start my changes from the Statement. Thus, I change the following code:
1 //GIVEN
2 var dispatch = new DataDispatch();
to:
1 //GIVEN
2 var dispatch = new DataDispatch(channel); //doesn't compile
I use a channel variable as if it was already defined in the Statement body and as if the constructor already required it. Of course, none of these is true yet. This leads my compiler to give me more compile errors. For me, this is a valuable source of feedback which I need to progress further. The first thing the compiler tells me to do is to introduce a channel variable. Again, I use my IDE to generate it for me. This time, however, the result of the generation is:
1 object channel;
The IDE could not guess the correct type of channel (which would be Channel) and made it an object, because I haven’t created the Channel type yet.
First, I’ll introduce the Channel interface by changing the declaration object channel; into Channel channel;. This will give me another compile error, as the Channel type does not exist. Thankfully, creating it is just one IDE click away (e.g. in Resharper, I place my cursor at the non-existent type, press ALT + ENTER and pick an option to create it as an interface.). Doing this will give me:
1 public interface Channel
2 {
3
4 }
which is enough to get past this particular compiler error, but then I get another one – nothing is assigned to the channel variable. Again, I have to turn my thinking on again. Luckily, this time I can lean on a simple rule: in my design, Channel is a role and, as mentioned in the last chapter, I use mocks to play the roles of my collaborators. So the conclusion is to use a mock. By applying this rule, I change the following line:
1 Channel channel;
to:
1 var channel = Substitute.For<Channel>();
The last compiler error I need to address to fully introduce the Channel collaborator is to make the DataDispatch constructor accept the channel as its argument. For now DataDispatch uses an implicit parameterless constructor. I generate a new one, again, using my IDE. I go to the place where the constructor is called with the channel as argument and tell my IDE to correct the constructor signature for me based on this usage. This way I get a constructor code inside the DataDispatch class:
1 public DataDispatch(Channel channel)
2 {
3
4 }
Note that the constructor doesn’t do anything with the channel yet. I could create a new field and assign the channel to it, but I don’t need to do that yet, so I decide I can wait a little bit longer.
Taking a bird’s-eye view on my Statement, I currently have:
1 [Fact] public void
2 ShouldXXXXXXXXXYYY() //TODO give a better name
3 {
4 //GIVEN
5 byte[] data; // does not compile yet
6 var channel = Substitute.For<Channel>();
7 var dispatch = new DataDispatch(channel);
8
9 //WHEN
10 dispatch.ApplyTo(data);
11
12 //THEN
13 Assert.True(false); //TODO state expectations
14 }
This way, I defined a Channel collaborator and introduced it first in my Statement, and then in the production code.
Specifying expectations
The compiler and my TODO list point out that I still have three tasks to accomplish for the current Statement:
- define
datavariable, - name my Statement and
- state my expectations (the
THENsection of the Statement)
I can do them in any order I see fit, so I pick the last task from the list - stating the expected behavior.
To specify what is expected from DataDispatch, I have to answer myself four questions:
- What are the obligations of
DataDispatch? - What is the purpose of
DataDispatch? - Who are the collaborators that need to receive messages from
DataDispatch? - What is the behavior of
DataDispatchthat I need to specify?
My answers to these questions are:
-
DataDispatchis obligated to send data as long as it is valid. In case of invalid data, it throws an exception. That’s two behaviors. As I only specify a single behavior per Statement, I need to pick one of them. I pick the first one (which I will call “the happy path” from now on), adding the second one to my TODO list:1//TODO: specify a behavior where sending data2// through a channel raises an exception - The purpose of
DataDispatchis to manage connection lifetime while sending data received via theApplyTo()method. Putting it together with the answer to the last question, what I would need to specify is howDataDispatchmanages this lifetime during the “happy path” scenario. The rest of the logic which I need to fulfill the obligation ofDataDispatchis outside the scope of the current Statement as I decided to push it to collaborators. - I already defined one collaborator and called it
Channel. As mentioned in the last chapter, in unit-level Statements, I fill my collaborators’ roles with mocks and specify what messages they should receive. Thus, I know that theTHENsection will describe the messages that theChannelrole (played by a mock object) is expected to receive from myDataDispatch. - Now that I know the scenario, the purpose and the collaborators, I can define my expected behavior in terms of those things. My conclusion is that I expect
DataDispatchto properly manage (purpose) aChannel(collaborator) in a “happy path” scenario where the data is sent without errors (obligation). As channels are typically opened before they are used and are closed afterwards, then what myDataDispatchis expected to do is to open the channel, then push data through it, and then close it.
How to implement such expectations? Implementation-wise, what I expect is that DataDispatch:
- makes correct calls on the
Channelcollaborator (open, send, close) - with correct arguments (the received data)
- in the correct order (cannot e.g. call close before open)
- correct number of times (e.g. should not send the data twice)
I can specify that using NSubstitute’s Received.InOrder() syntax. I will thus use it to state that the three methods are expected to be called in a specific order. Wait, what methods? After all, our Channel interface looks like this:
1 public interface Channel
2 {
3
4 }
so there are no methods here whatsoever. The answer is – just like I discovered the need for the Channel interface and then brought it to life afterward, I now discovered that I need three methods: Open(), Send() and Close(). The same way as I did with the Channel interface, I will use them in my Statement as if they existed:
1 [Fact] public void
2 ShouldXXXXXXXXXYYY() //TODO give a better name
3 {
4 //GIVEN
5 byte[] data; // does not compile yet
6 var channel = Substitute.For<Channel>();
7 var dispatch = new DataDispatch(channel);
8
9 //WHEN
10 dispatch.ApplyTo(data);
11
12 //THEN
13 Received.InOrder(() =>
14 {
15 channel.Open(); //doesn't compile
16 channel.Send(data); //doesn't compile
17 channel.Close(); //doesn't compile
18 });
19 }
and then pull them into existence using my IDE and its shortcut for generating missing classes and methods. This way, I get:
1 public interface Channel
2 {
3 void Open();
4 void Send(byte[] data);
5 void Close();
6 }
Now I have only two things left on my list – giving the Statement a good name and deciding what the data variable should hold. I’ll go with the latter as it is the last thing that prevents the compiler from compiling and running my Statement and I expect it will give me more useful feedback.
The data variable
What should I assign to the data variable? Time to think about how much the DataDispatch needs to know about the data it pushes through the channel. I decide that DataDispatch should work with any data – its purpose is to manage the connection after all – it does not need to read or manipulate the data to do this. Someone, somewhere, probably needs to validate this data, but I decide that if I added validation logic to the DataDispatch, it would break the single-purposeness. So I push validation further to the Channel interface, as the decision to accept the data or not depends on the actual implementation of sending logic. Thus, I define the data variable in my Statement as just Any.Array<byte>():
1 [Fact] public void
2 ShouldXXXXXXXXXYYY() //TODO give a better name
3 {
4 //GIVEN
5 var data = Any.Array<byte>();
6 var channel = Substitute.For<Channel>();
7 var dispatch = new DataDispatch(channel);
8
9 //WHEN
10 dispatch.ApplyTo(data);
11
12 //THEN
13 Received.InOrder(() =>
14 {
15 channel.Open();
16 channel.Send(data);
17 channel.Close();
18 });
19 }
Good name
The Statement now compiles and runs (it is currently false, of course, but I’ll get to that), so what I need is to give this Statement a better name. I’ll go with ShouldSendDataThroughOpenChannelThenCloseWhenAskedToDispatch. This was the last TODO on the Specification side, so let’s see the full Statement code:
1 [Fact] public void
2 ShouldSendDataThroughOpenChannelThenCloseWhenAskedToDispatch()
3 {
4 //GIVEN
5 var data = Any.Array<byte>();
6 var channel = Substitute.For<Channel>();
7 var dispatch = new DataDispatch(channel);
8
9 //WHEN
10 dispatch.ApplyTo(data);
11
12 //THEN
13 Received.InOrder(() =>
14 {
15 channel.Open();
16 channel.Send(data);
17 channel.Close();
18 });
19 }
Failing for the correct reason
The Statement I just wrote can now be evaluated and, as expected, it is false. This is because the current implementation of the ApplyTo method throws a NotImplementedException:
1 public class DataDispatch
2 {
3 public DataDispatch(Channel channel)
4 {
5
6 }
7
8 public void ApplyTo(byte[] data)
9 {
10 throw new NotImplementedException();
11 }
12 }
What I’d like to see before I start implementing the correct behavior is that the Statement is false because assertions (in this case – mock verifications) fail. So the part of the Statement that I would like to see throwing an exception is this one:
1 Received.InOrder(() =>
2 {
3 channel.Open();
4 channel.Send(data);
5 channel.Close();
6 });
but instead, I get an exception as early as:
1 //WHEN
2 dispatch.ApplyTo(data);
To make progress past the WHEN section, I need to push the production code a little bit further towards the correct behavior, but only as much as to see the expected failure. Thankfully, I can achieve it easily by going into the ApplyTo() method and removing the throw clause:
1 public void ApplyTo(byte[] data)
2 {
3
4 }
This alone is enough to see the mock verification making my Statement false. Now that I can see that the Statement is false for the correct reason, my next step is to put the correct implementation to make the Statement true.
Making the Statement true
I start with the DataDispatch constructor, which currently takes a Channel as a parameter, but doesn’t do anything with it:
1 public DataDispatch(Channel channel)
2 {
3
4 }
I want to assign the channel to a newly created field (I can do this using a single command in most IDEs). The code then becomes:
1 private readonly Channel _channel;
2
3 public DataDispatch(Channel channel)
4 {
5 _channel = channel;
6 }
This allows me to use the _channel in the ApplyTo() method that I’m trying to implement. Remembering that my goal is to open the channel, push the data and close the channel, I type:
1 public void ApplyTo(byte[] data)
2 {
3 _channel.Open();
4 _channel.Send(data);
5 _channel.Close();
6 }
Second behavior – specifying an error
The first Statement is implemented, so time for the second one – remember I put it on the TODO list a while ago so that I don’t forget about it:
1 //TODO: specify a behavior where sending data
2 // through a channel raises an exception
This behavior is that when the sending fails with an exception, the user of DataDispatch should receive an exception and the connection should be safely closed. Note that the notion of what “closing the connection” means is delegated to the Channel implementations, so when specifying the behaviors of DataDispatch I only need to care whether Channel’s Close() method is invoked correctly. The same goes for the meaning of “errors while sending data” – this is also the obligation of Channel. What we need to specify about DataDispatch is how it handles the sending errors regarding its user and its Channel.
Starting with a good name
This time, I choose the strategy of starting with a good name, because I feel I have a much better understanding of what behavior I need to specify than with my previous Statement. I pick the following name to state the expected behavior:
1 public void
2 ShouldRethrowExceptionAndCloseChannelWhenSendingDataFails()
3 {
4 //...
5 }
Before I start dissecting the name into useful code, I start by stating the bleedy obvious (note that I’m mixing two strategies of starting from false Statement now – I didn’t say you can’t do that now, did I?). Having learned a lot by writing and implementing the previous Statement, I know for sure that:
- I need to work with
DataDispatchagain. - I need to pass a mock of
ChanneltoDataDispatchconstructor. -
Channelrole will be played by a mock object. - I need to invoke the
ApplyTo()method. - I need some kind of invalid data (although I don’t know yet what to do to make it “invalid”).
I write that down as code:
1 public void
2 ShouldRethrowExceptionAndCloseChannelWhenSendingDataFails()
3 {
4 //GIVEN
5 var channel = Substitute.For<Channel>();
6 var dataDispatch = new DataDispatch(channel);
7 byte[] invalidData; //doesn't compile
8
9 //WHEN
10 dataDispatch.ApplyTo(invalidData);
11
12 //THEN
13 Assert.True(false); //no expectations yet
14 }
Expecting that channel is closed
I also know that one aspect of the expected behavior is closing the channel. I know how to write this expectation – I can use the Received() method of NSubstitute on the channel mock. This will, of course, go into the //THEN section:
1 //THEN
2 channel.Received(1).Close(); //added
3 Assert.True(false); //not removing this yet
4 }
I used Received(1) instead of just Received(), because attempting to close the channel several times might cause trouble, so I want to be explicit on the expectation that the DataDispatch should close the channel exactly once. Another thing – I am not removing the Assert.True(false) yet, as the current implementation already closes the channel and so the Statement could become true if not for this assertion (if it compiled, that is). I will remove this assertion only after I fully define the behavior.
Expecting exception
Another thing I expect DataDispatch to do in this behavior is to rethrow any sending errors, which are reported as exceptions thrown by Channel from the Send() method.
To specify that I expect an exception in my Statement, I need to use a special assertion called Assert.Throws<>() and pass the code that should throw the exception as a lambda:
1 //WHEN
2 Assert.Throws<Exception>(() =>
3 dataDispatch.ApplyTo(invalidData));
Defining invalid data
My compiler shows me that the data variable is undefined. OK, now the time has come to define invalid data.
First of all, remember that DataDispatch cannot tell the difference between valid and invalid data - this is the purpose of the Channel as each Channel implementation might have different criteria for data validation. In my Statement, I use a mock to play the channel role, so I can just tell my mock that it should treat the data I define in my Statement as invalid. Thus, the value of the data itself is irrelevant as long as I configure my Channel mock to act as if it was invalid. This means that I can just define the data as any byte array:
1 var invalidData = Any.Array<byte>();
I also need to write down the assumption of how the channel will behave given this data:
1 //GIVEN
2 ...
3 var exceptionFromChannel = Any.Exception();
4 channel.When(c => c.Send(invalidData)).Throw(exceptionFromChannel);
Note that the place where I configure the mock to throw an exception is the //GIVEN section. This is because any predefined mock behavior is my assumption. By pre-canning the method outcome, in this case, I say “given that channel for some reason rejects this data”.
Now that I have the full Statement code, I can get rid of the Assert.True(false) assertion. The full Statement looks like this:
1 public void
2 ShouldRethrowExceptionAndCloseChannelWhenSendingDataFails()
3 {
4 //GIVEN
5 var channel = Substitute.For<Channel>();
6 var dataDispatch = new DataDispatch(channel);
7 var data = Any.Array<byte>();
8 var exceptionFromChannel = Any.Exception();
9
10 channel.When(c => c.Send(data)).Throw(exceptionFromChannel);
11
12 //WHEN
13 var exception = Assert.Throws<Exception>(() =>
14 dataDispatch.ApplyTo(invalidData));
15
16 //THEN
17 Assert.Equal(exceptionFromChannel, exception);
18 channel.Received(1).Close();
19 }
Now, it may look a bit messy, but given my toolset, this will have to do. The Statement will now turn false on the second assertion. Wait, the second? What about the first one? Well, the first assertion says that an exception should be rethrown and methods in C# rethrow the exception by default, not requiring any implementation on my part2. Should I just accept it and go on? Well, I don’t want to. Remember what I wrote in the first part of the book – we need to see each assertion fail at least once. An assertion that passes straight away is something we should be suspicious about. What I need to do now is to temporarily break the behavior so that I can see the failure. I can do that in (at least) two ways:
- By going to the Statement and commenting out the line that configures the
Channelmock to throw an exception. - By going to the production code and surrounding the
channel.Send(data)statement with a try-catch block.
Either way would do, but I typically prefer to change the production code and not alter my Statements, so I chose the second way. By wrapping the Send() invocation with try and empty catch, I can now observe the assertion fail, because an exception is expected but none comes out of the dataDispatch.ApplyTo() invocation. Now I’m ready to undo my last change, confident that my Statement describes this part of the behavior well and I can focus on the second assertion, which is:
1 channel.Received(1).Close();
This assertion fails because my current implementation of the ApplyTo() method is:
1 _channel.Open();
2 _channel.Send(data);
3 _channel.Close();
and an exception thrown from the Send() method interrupts the processing, instantly exiting the method, so Close() is never called. I can change this behavior by using try-finally block to wrap the call to Send()3:
1 _channel.Open();
2 try
3 {
4 _channel.Send(data);
5 }
6 finally
7 {
8 _channel.Close();
9 }
This makes my second Statement true and concludes this example. If I were to go on, my next step would be to implement the newly discovered Channel interface, as currently, it has no implementation at all.
Summary
In this chapter, I delved into writing mock-based Statements and developing classes in a test-first manner. I am not showing this example as a prescription or any kind of “one true way” of test-driving such implementation - some things could’ve been done differently. For example, there were many situations where I got several TODO items pointed out by my compiler or my false Statement. Depending on many factors, I might’ve approached them in a different order. Or in the second behavior, I could’ve defined data as Any.Array<byte>() right from the start (and left a TODO item to check on it later and confirm whether it can stay this way) to get the Statement to a compiling state quicker.
Another interesting point was the moment when I discovered the Channel interface – I’m aware that I slipped over it by saying something like “we can see that the class has too many purposes, then magic happens and then we’ve got an interface to delegate parts of the logic to”. This “magic happens” or, as I mentioned, “interface discovery”, is something I will expand on in the following chapters.
You might’ve noticed that this chapter was longer than the last one, which may lead you to a conclusion that TDD complicates things rather than simplifying them. There were, however, several factors that made this chapter longer:
- In this chapter, I specified two behaviors (a “happy path” plus error handling), whereas in the last chapter I only specified one (the “happy path”).
- In this chapter, I designed and implemented the
DataDispatchclass and discovered theChannelinterface whereas in the last chapter they were given to me right from the start. - Because I assume the test-first way of writing Statements may be less familiar to you, I took my time to explain my thought process in more detail.
So don’t worry – when one gets used to it, the process I described in this chapter typically takes several minutes at worst.
Test-driving at the input boundary
In this chapter, we’ll be joining Johnny and Benjamin again as they try to test-drive a system starting from its input boundaries. This will hopefully demonstrate how abstractions are pulled from need and how roles are invented at the boundary of a system. The further chapters will explore the domain model. This example makes several assumptions:
- In this story, Johnny is a super-programmer, who never makes mistakes. In real-life TDD, people make mistakes and correct them, sometimes they go back and forth thinking about tests and design. Here, Johnny gets everything right the first time. Although I know this is a drop on realism, I hope that it will help my readers in observing how some TDD mechanics work. This is also why Johnny and Benjamin will not need to refactor anything in this chapter.
- There will be no Statements written on higher than unit level. This means that Johnny and Benjamin will do TDD using unit-level Statements only. This is why they will need to do some things they could avoid if they could write a higher-level Statement. A separate part of this book will cover working with different levels of Statements at the same time.
- This chapter (and several next ones) will avoid the topic of working with any I/O, randomness, and other hard-to-test stuff. For now, I want to focus on test-driving pure code-based logic.
With this out of our way, let’s join Johnny and Benjamin and see what kind of issue they are dealing with and how they try to solve it using TDD.
Fixing the ticket office
Johnny: What do you think about trains, Benjamin?
Benjamin: Are you asking because I was traveling by train yesterday to get here? Well, I like it, especially after some of the changes that happened over the recent years. I truly think that today’s railways are modern and passenger-friendly.
Johnny: And about the seat reservation process?
Benjamin: Oh, that… I mean, why didn’t they still automate the process? Is this even thinkable that in the 21st century I cannot reserve a seat through the internet?
Johnny: I kinda hoped you’d say that because our next assignment is to do exactly that.
Benjamin: You mean reserving seats through the internet?
Johnny: Yes, the railroad company hired us.
Benjamin: You’re kidding me, right?
Johnny: No, I’m telling the truth.
Benjamin: No way.
Johnny: Take your smartphone and check your e-mail. I already forwarded the details to you.
Benjamin: Hey, that looks legit. Why didn’t you tell me earlier?
Johnny: I’ll explain on the way. Come on, let’s go.
Initial objects
Benjamin: do we have any sort of requirements, stories or whatever to work with?
Johnny: Yes, I’ll explain some as I walk you through the input and output data structures. It will be enough to get us going.
Request
Johnny: Somebody’s already written the part that accepts an HTTP request and maps it to the following structure:
1 public class ReservationRequestDto
2 {
3 public readonly string TrainId;
4 public readonly uint SeatCount;
5
6 public ReservationRequestDto(string trainId, uint seatCount)
7 {
8 TrainId = trainId;
9 SeatCount = seatCount;
10 }
11 }
Benjamin: I see… Hey, why does the ReservationRequestDto name has Dto in it? What is it?
Johnny: The suffix Dto means that this class represents a Data Transfer Object (in short, DTO)4. Its role is just to transfer data across the process boundaries.
Benjamin: So you mean it is just needed to represent some kind of XML or JSON that is sent to the application?
Johnny: Yes, you could say that. The reason people typically place Dto in these names is to communicate that these data structures are special - they represent an outside contract and cannot be freely modified like other objects.
Benjamin: Does it mean that I can’t touch them?
Johnny: It means that if you did touch them, you’d have to make sure they are still correctly mapped from outside data, like JSON or XML.
Benjamin: So I’d better not mess with them. Cool, and what about the train ID?
Johnny: It represents a train. The client application that uses the backend that we’ll write will understand these IDs and use them to communicate with us.
Benjamin: Cool, what’s next?
Johnny: The client application tells our service how many seats it needs to reserve, but doesn’t say where. This is why there’s only a seatCount parameter. Our service must determine which seats to pick.
Benjamin: So if a couple wants to reserve two seats, they can be in different coaches?
Johnny: Yes, however, there are some preference rules that we need to code in, like, if we can, a single reservation should have all seats in the same coach. I’ll fill you in on the rules later.
Response
Benjamin: Do we return something to the client app?
Johnny: Yes, we need to return a response, which, no surprise, is also modeled in the code as a DTO. This response represents the reservation made:
1 public class ReservationDto
2 {
3 public readonly string TrainId;
4 public readonly string ReservationId;
5 public readonly List<TicketDto> PerSeatTickets;
6
7 public ReservationDto(
8 string trainId,
9 List<TicketDto> perSeatTickets,
10 string reservationId)
11 {
12 TrainId = trainId;
13 PerSeatTickets = perSeatTickets;
14 ReservationId = reservationId;
15 }
16 }
Benjamin: I see that there’s a train ID, which is… the same as the one in the request, I suppose?
Johnny: Right. It’s used to correlate the request and the response.
Benjamin: …and there is a reservation ID - does our service generate that?
Johnny: Correct.
Benjamin: but the PerSeatTickets field… it is a list of TicketDto, which as I understand is one of our custom types. Where is it?
Johnny: I forgot to show it to you. TicketDto is defined as:
1 public class TicketDto
2 {
3 public readonly string Coach;
4 public readonly int SeatNumber;
5
6 public TicketDto(string coach, int seatNumber)
7 {
8 Coach = coach;
9 SeatNumber = seatNumber;
10 }
11 }
so it has a coach name and a seat number, and we have a list of these in our reservation.
Benjamin: So a single reservation can contain many tickets and each ticket is for a single place in a specific coach, right?
Johnny: Yes.
Ticket Office class
Benjamin: The classes we just saw - are they representations of some kind of JSON or XML?
Johnny: Yes, but we don’t have to write the code to do it. As I mentioned earlier, someone already took care of that.
Benjamin: Lucky us.
Johnny: Our work starts from the point where the deserialized data is passed to the application as a DTO. The request entry point is in a class called TicketOffice:
1 [SomeKindOfController]
2 public class TicketOffice
3 {
4 [SuperFrameworkMethod]
5 public ReservationDto MakeReservation(ReservationRequestDto requestDto)
6 {
7 throw new NotImplementedException("Not implemented");
8 }
9 }
Johnny: I see that the class is decorated with attributes specific to a web framework, so we will probably not implement the use case directly in the MakeReservation method to avoid coupling our use case logic to the code that has to meet the requirements of a specific framework.
Benjamin: So what you’re saying is that you’re trying to keep the TicketOffice class away from the application logic as much you can?
Johnny: Yes. I only need it to wrap all the data into appropriate abstractions which I design to match my preferences, not the framework. Then, in these abstractions, I can freely code my solution the way I like.
Bootstrap
Benjamin: Are we ready to go?
Johnny: Typically, if I were you, I would like to see one more place in the code.
Benjamin: Which is..?
Johnny: The composition root, of course.
Benjamin: Why would I like to see a composition root?
Johnny: The first reason is that it is very close to the entry point of the application, so it is a chance for you to see how the application manages its dependencies. The second reason is that each time we will be adding a new class that has the same lifespan as the application, we will need to go to the composition root and modify it. Sooo I’d probably like to know where it is and how I should work with it.
Benjamin: I thought I could find that later, but while we’re at it, can you show me the composition root?
Johnny: Sure, it’s here, in the Application class:
1 public class Application
2 {
3 public static void Main(string[] args)
4 {
5 new WebApp(
6 new TicketOffice()
7 ).Host();
8 }
9 }
Benjamin: Good to see it doesn’t require us to use any fancy reflection-based mechanism for composing objects.
Johnny: Yes, we’re lucky about that. We can just create the objects with the new operator and pass them to the framework. Another important piece of information is that we have a single TicketOffice instance to handle all the requests. This means that we cannot store any state related to a single request inside this instance as it will come into conflict with other requests.
Writing the first Statement
Johnny: Anyway, I think we’re ready to start.
Benjamin: But where do we start from? Should we write some kind of a class called “Reservation” or “Train” first?
Johnny: No, what we will do is we will start from the inputs and work our way towards the inside of the application. Then, if necessary, to the outputs.
Benjamin: I don’t think I understand what you’re talking about. Do you mean this “outside-in” approach that you mentioned yesterday?
Johnny: Yes, and don’t worry if you didn’t get what I said, I will explain as we go. For now, the only thing I mean by it is that we will follow the path of the request as it comes from the outside of our application and start implementing in the first place where the request is not handled as it should be. Specifically, this means we start at:
1 [SomeKindOfController]
2 public class TicketOffice
3 {
4 [SuperFrameworkMethod]
5 public ReservationDto MakeReservation(ReservationRequestDto requestDto)
6 {
7 throw new NotImplementedException("Not implemented");
8 }
9 }
Benjamin: Why?
Johnny: Because this is the place nearest to the request entry point where the behavior differs from the one we expect. As soon as the request reaches this point, its handling will stop and an exception will be thrown. We need to alter this code if we want the request to go any further.
Benjamin: I see… so… if we didn’t have the request deserialization code in place already, we’d start there because that would be the first place where the request would get stuck on its way towards its goal, right?
Johnny: Yes, you got it.
Benjamin: And… we start with a false Statement, no?
Johnny: Yes, let’s do that.
First Statement skeleton
Benjamin: Don’t tell me anything, I’ll try doing it myself.
Johnny: Sure, as you wish.
Benjamin: The first thing I need to do is to add an empty Specification for the TicketOffice class:
1 public class TicketOfficeSpecification
2 {
3 //TODO add a Statement
4 }
Then, I need to add my first Statement. I know that in this Statement, I need to create an instance of the TicketOffice class and call the MakeReservation method, since it’s the only method in this class and it’s not implemented.
Johnny: so what strategy do you use for starting with a false Statement?
Benjamin: “invoke a method when you have one”, as far as I remember.
Johnny: So what’s the code going to look like?
Benjamin: for starters, I will do a brain dump just as you taught me. After stating all the bleedy obvious facts, I get:
1 [Fact]
2 public void ShouldXXXXX() //TODO better name
3 {
4 //WHEN
5 var ticketOffice = new TicketOffice();
6
7 //WHEN
8 ticketOffice.MakeReservation(requestDto);
9
10 //THEN
11 Assert.True(false);
12 }
Johnny: Good… my… apprentice…
Benjamin: What?
Johnny: Oh, nevermind… anyway, the code doesn’t compile now, since this line:
1 ticketOffice.MakeReservation(requestDto);
uses a variable requestDto that does not exist. Let’s generate it using our IDE!
Benjamin: By the way, I wanted to ask about this exact line. Making it compile is something we need to do to move on. Weren’t we supposed to add a TODO comment for things we need to get back to? Just as we did with the Statement name, which was:
1 public void ShouldXXXXX() //TODO better name
Johnny: My opinion is that this is not necessary, because the compiler, by failing on this line, has already created a sort of TODO item for us, just not on our TODO list but the compiler error log. This is different than e.g. a need to change a method’s name, which the compiler will not remind us about.
Benjamin: So my TODO list is composed of compile errors, false Statements, and the items I manually mark as TODO? Is this how I should understand it?
Johnny: Exactly. Going back to the requestDto variable, let’s create it.
Benjamin: Sure. It came out like this:
1 ReservationRequestDto requestDto;
We need to assign something to the variable.
Johnny: Yes, and since it’s a DTO, it is certainly not going to be a mock.
Benjamin: You mean we don’t mock DTOs?
Johnny: No, there’s no need. DTOs are, by definition, data structures and mocking involves polymorphism which applies to behavior rather than data. Later I will explain it in more detail. For now, just accept my word on it.
Benjamin: Sooo… if it’s not going to be a mock, then let’s generate an anonymous value for it using the Any.Instance<>() method.
Johnny: That is exactly what I would do.
Benjamin: So I will change this line:
1 ReservationRequestDto requestDto;
to:
1 var requestDto = Any.Instance<ReservationRequestDto>();
Setting up the expectation
Johnny: Yes, and now the Statement compiles, and it seems to be false. This is because of this line:
1 Assert.True(false);
Benjamin: so we change this false to true and we’re done here, right?
Johnny: …Huh?
Benjamin: Oh, I was just pulling your leg. What I really wanted to say is: let’s turn this assertion into something useful.
Johnny: Phew, don’t scare me like that. Yes, this assertion needs to be rewritten. And it so happens that when we look at the following line:
1 ticketOffice.MakeReservation(requestDto);
it doesn’t make any use of the return value of MakeReservation() while it’s evident from the signature that its return type is a ReservationDto. Look:
1 public ReservationDto MakeReservation(ReservationRequestDto requestDto)
In our Statement, we don’t do anything with it.
Benjamin: Let me guess, you want me to go to the Statement, assign this return value to a variable and then assert its equality to… what exactly?
Johnny: For now, to an expected value, which we don’t know yet, but we will worry about it later when it really blocks us.
Benjamin: This is one of those situations where we need to imagine that we already have something we don’t, right?. Ok, here goes:
1 [Fact]
2 public void ShouldXXXXX() //TODO better name
3 {
4 //WHEN
5 var requestDto = Any.Instance<ReservationRequestDto>();
6 var ticketOffice = new TicketOffice();
7
8 //WHEN
9 var reservationDto = ticketOffice.MakeReservation(requestDto);
10
11 //THEN
12 //doesn't compile - we don't have expectedReservationDto yet:
13 Assert.Equal(expectedReservationDto, reservationDto);
14 }
There, I did what you asked. So please explain to me now how did it get us any closer to our goal?
Johnny: We transformed our problem from “what assertion to write” into “what is the reservation that we expect”. This is indeed a step in the right direction.
Benjamin: Please enlighten me then - what is “the reservation that we expect”?
Johnny: For now, the Statement is not compiling at all, so to go any step further, we can just introduce an expectedReservationDto as an anonymous object. Thus, we can just write in the GIVEN section:
1 var expectedReservationDto = Any.Instance<ReservationDto>();
and it will make the following code compile:
1 //THEN
2 Assert.Equal(expectedReservationDto, reservationDto);
Benjamin: But this assertion will fail anyway…
Johnny: That’s still better than not compiling, isn’t it?
Benjamin: Well, if you put it this way… Now our problem is that the expected value from the assertion is something the production code doesn’t know about - this is just something we created in our Statement. This means that this assertion does not assert the outcome of the behavior of the production code. How do we solve this?
Johnny: This is where we need to exercise our design skills to introduce some new collaborators. This task is hard at the boundaries of application logic where we need to draw the collaborators not from the domain, but rather think about design patterns that will allow us to reach our goals. Every time we enter our application logic, we do so with a specific use case in mind. In this particular example, our use case is “making a reservation”. A use case is typically represented by either a method in a facade5 or a command object6. Commands are a bit more complex but more scalable. If making a reservation was our only use case, it probably wouldn’t make sense to use it. But as we already have more high priority requests for features, I believe we can assume that commands will be a better fit.
Benjamin: So you propose to use a more complex solution - isn’t that “big design up front”?
Johnny: I believe that it isn’t. Remember I’m using just a bit more complex solution. The cost of implementation is only a bit higher as well as the cost of maintenance in case I’m wrong. If for some peculiar reason someone says tomorrow that they don’t need the rest of the features at all, the increase in complexity will be negligible taking into account the small size of the overall codebase. If, however, we add more features, then using commands will save us some time in the longer run. Thus, given what I know, I am not adding this to support speculative new features, but to make the code easier to modify in the long run. I agree though that choosing just enough complexity for a given moment is a difficult task.
Benjamin: I still don’t get how introducing a command is going to help us here. Typically, a command has an Execute() method that doesn’t return anything. How then will it give us the response that we need to return from the MakeReservation()? And also, there’s another issue: how is this command going to be created? It will probably require the request passed as one of its constructor parameters. This means we cannot just pass the command to the TicketOffice’s constructor as the first time we can access the request is when the MakeReservation() method is invoked.
Johnny: Yes, I agree with both of your concerns. Thankfully, when you choose to go with commands, typically there are standard solutions to the problems you mentioned. The commands can be created using factories and they can convey their results using a pattern called collecting parameter7 - we will pass an object inside the command to gather all the events from handling the use case and then be able to prepare a response for us.
Introducing a reservation in progress collaborator
Johnny: Let’s start with the collecting parameter, which will represent a domain concept of a reservation in progress. We need it to collect the data necessary to build a response DTO. Thus, what we currently know about it is that it’s going to be converted to a response DTO at the very end. All of the three objects: the command, the collecting parameter, and the factory, are collaborators, so they will be mocks in our Statement.
Benjamin: Ok, lead the way.
Johnny: Let’s start with the GIVEN section. Here, we need to say that we assume that the collecting parameter mock, let’s call it reservationInProgress will give us the expectedReservationDto (which is already defined in the body of the Statement) when asked:
1 //GIVEN
2 //...
3 reservationInProgress.ToDto().Returns(expectedReservationDto);
Of course, we don’t have the reservationInProgress yet, so we need to introduce it. As I explained earlier, this needs to be a mock, because otherwise, we wouldn’t be able to call Returns() on it:
1 ///GIVEN
2 var reservationInProgress = Substitute.For<ReservationInProgress>();
3 //...
4 reservationInProgress.ToDto().Returns(expectedReservationDto);
5 //...
Now, the Statement does not compile because the ReservationInProgress interface that I just used in the mock definition is not introduced yet.
Benjamin: In other words, you just discovered that you need this interface.
Johnny: Exactly. What I’m currently doing is I’m pulling abstractions and objects into my code as I need them. And my current need is to have the following interface in my code:
1 public interface ReservationInProgress
2 {
3
4 }
Now, the Statement still doesn’t compile, because there’s this line:
1 reservationInProgress.ToDto().Returns(expectedReservationDto);
which requires the ReservationInProgress interface to have a ToDto() method, but for now, this interface is empty. After adding the required method, it looks like this:
1 public interface ReservationInProgress
2 {
3 ReservationDto ToDto();
4 }
and the Statement compiles again, although it is still a false one.
Benjamin: Ok. Now give me a second to grasp the full Statement in its current state.
Johnny: Sure, take your time, this is how it currently looks like:
1 [Fact]
2 public void ShouldXXXXX() //TODO better name
3 {
4 //WHEN
5 var requestDto = Any.Instance<ReservationRequestDto>();
6 var ticketOffice = new TicketOffice();
7 var reservationInProgress = Substitute.For<ReservationInProgress>();
8 var expectedReservationDto = Any.Instance<ReservationDto>();
9
10 reservationInProgress.ToDto().Returns(expectedReservationDto);
11
12 //WHEN
13 var reservationDto = ticketOffice.MakeReservation(requestDto);
14
15 //THEN
16 Assert.Equal(expectedReservationDto, reservationDto);
17 }
Introducing a factory collaborator
Benjamin: I think I managed to catch up. Can I grab the keyboard?
Johnny: I was about to suggest it. Here.
Benjamin: Thanks. Looking at this Statement, we have this ReservationInProgress all setup and created, but this mock of ours is not passed to the TicketOffice at all. So how should the TicketOffice use our pre-configured reservationInProgress?
Johnny: Remember our discussion about separating object use from construction?
Benjamin: I guess I know what you’re getting at. The TicketOffice should somehow get an already created ReservationInProgress object from the outside. It can get it e.g. through a constructor or from a factory.
Johnny: Yes, and if you look at the lifetime scope of our TicketOffice, which is created once at the start of the application, it can’t accept a ReservationInProgress through a constructor, because every time a new reservation request is made, we want to have a new ReservationInProgress, so passing it through a TicketOffice constructor would force us to create a new TicketOffice every time as well. Thus, the solution that better fits our current situation is…
Benjamin: A factory, right? You’re suggesting that instead of passing a ReservationInProgress through a constructor, we should rather pass something that knows how to create ReservationInProgress instances?
Johnny: Exactly.
Benjamin: How to write it in the Statement?
Johnny: First, write only what you need. The factory needs to be a mock because we need to configure it so that when asked, it returns our ReservationInProgress mock. So let’s write that return configuration first, pretending we already have the factory available in our Statement body.
Benjamin: Let me see… that should do it:
1 //GIVEN
2 ...
3 reservationInProgressFactory.FreshInstance().Returns(reservationInProgress);
Johnny: Nice. Now the code does not compile, because we don’t have a reservationInProgressFactory. So let’s create it.
Benjamin: And, as you said earlier, it should be a mock object. Then this will be the definition:
1 var reservationInProgressFactory
2 = Substitute.For<ReservationInProgressFactory>();
For the need of this line of code, I pretended that I have an interface called ReservationInProgressFactory, and, let me guess, you want me to introduce this interface now?
Johnny: (smiles)
Benjamin: Alright. Here:
1 public interface ReservationInProgressRepository
2 {
3
4 }
And now, the compiler tells us that we don’t have the FreshInstance() method, so let me introduce it:
1 public interface ReservationInProgressRepository
2 {
3 ReservationInProgress FreshInstance();
4 }
Good, the compiler doesn’t complain anymore, but the Statement fails with a NotImplementedException.
Johnny: Yes, this is because the current body of the MakeReservation() method of the TicketOffice class looks like this:
1 public ReservationDto MakeReservation(ReservationRequestDto requestDto)
2 {
3 throw new NotImplementedException("Not implemented");
4 }
Expanding the ticket office constructor
Benjamin: So should we implement this now?
Johnny: We still have some stuff to do in the Statement.
Benjamin: Like..?
Johnny: For example, the ReservationInProgressFactory mock that we just created is not passed to the TicketOffice constructor yet, so there is no way for the ticket office to use this factory.
Benjamin: Ok, so I’ll add it. The Statement will change in this place:
1 var reservationInProgressFactory
2 = Substitute.For<ReservationInProgressFactory>();
3 var ticketOffice = new TicketOffice();
to:
1 var reservationInProgressFactory
2 = Substitute.For<ReservationInProgressFactory>();
3 var ticketOffice = new TicketOffice(reservationInProgressFactory);
and a constructor needs to be added to the TicketOffice class:
1 public TicketOffice(ReservationInProgressFactory reservationInProgressFactory)
2 {
3
4 }
Johnny: Agreed. And, we need to maintain the composition root which just stopped compiling. This is because the constructor of a TicketOffice is invoked there and it needs an update as well:
1 public class Application
2 {
3 public static void Main(string[] args)
4 {
5 new WebApp(
6 new TicketOffice(/* compile error - instance missing */)
7 ).Host();
8 }
9 }
Benjamin: But what should we pass? We have an interface, but no class of which we could make an instance.
Johnny: We need to create the class. Typically, if I have an idea about the name of the required class, I create the class by that name. If I don’t have any idea on how to call it yet, I can call it e.g. TodoReservationInProgressFactory and leave a TODO comment to get back to it later. For now, we just need this class to compile the code. It’s still out of the scope of our current Statement.
Benjamin: So maybe We could pass a null so that we have new TicketOffice(null)?
Johnny: We could, but that’s not my favorite option. I typically just create the class. One of the reasons is that the class will need to implement an interface to compile and then we will need to introduce a method which will, by default, throw a NotImplementedException and these exceptions will end up on my TODO list as well.
Benjamin: Ok, that sounds reasonable to me. So this line:
1 new TicketOffice(/* compile error - instance missing */)
becomes:
1 new TicketOffice(
2 new TodoReservationInProgressFactory()) //TODO change the name
And it doesn’t compile, because we need to create this class so let me do it:
1 public class TodoReservationInProgressFactory : ReservationInProgressFactory
2 {
3 }
Johnny: It still doesn’t compile, because the interface ReservationInProgressFactory has some methods that we need to implement. Thankfully, we can do this with a single IDE command and get:
1 public class TodoReservationInProgressFactory : ReservationInProgressFactory
2 {
3 public ReservationInProgress FreshInstance()
4 {
5 throw new NotImplementedException();
6 }
7 }
and, as I mentioned a second ago, this exception will end up on my TODO list, reminding me that I need to address it.
Now that the code compiles again, let’s backtrack to the constructor of TicketOffice:
1 public TicketOffice(ReservationInProgressFactory reservationInProgressFactory)
2 {
3
4 }
here, we could already assign the constructor parameter to a field, but it’s also OK to do it later.
Benjamin: Let’s do it later, I wonder how far we can get delaying work like this.
Introducing a command collaborator
Johnny: Sure. So let’s take a look at the Statement we’re writing. It seems we are missing one more expectation in our THEN section. if you look at the Statement’s full body as it is now:
1 [Fact]
2 public void ShouldXXXXX() //TODO better name
3 {
4 //WHEN
5 var requestDto = Any.Instance<ReservationRequestDto>();
6 var reservationInProgressFactory
7 = Substitute.For<ReservationInProgressFactory>();
8 var ticketOffice = new TicketOffice(reservationInProgressFactory);
9 var reservationInProgress = Substitute.For<ReservationInProgress>();
10 var expectedReservationDto = Any.Instance<ReservationDto>();
11
12 reservationInProgressFactory.FreshInstance().Returns(reservationInProgress);
13 reservationInProgress.ToDto().Returns(expectedReservationDto);
14
15 //WHEN
16 var reservationDto = ticketOffice.MakeReservation(requestDto);
17
18 //THEN
19 Assert.Equal(expectedReservation, reservationDto);
20 }
the only interaction between a TicketOffice and ReservationInProgress it describes is the former calling the ToDto method on the latter. So the question that we need to ask ourselves now is “how will the instance of ReservationInProgress know what ReservationDto to create when this method is called?”.
Benjamin: Oh right… the ReservationDto needs to be created by the ReservationInProgress based on the current state of the application and the data in the ReservationRequestDto, but the ReservationInProgress knows nothing about any of these things so far.
Johnny: Yes, filling the ReservationInProgress is one of the responsibilities of the application we are writing. If we did it all in the TicketOffice class, this class would surely have too much to handle and our Statement would grow immensely. So we need to push the responsibility of handling our use case further to other collaborating roles and use mocks to play those roles here.
Benjamin: So what do you propose?
Johnny: Remember our discussion from several minutes ago? Usually, when I push a use case-related logic to another object at the system boundary, I pick from among the Facade pattern and the Command pattern. Facades are simpler but less scalable, while commands are way more scalable and composable but a new command object needs to be created by the application each time a use case is triggered and a new command class needs to be created by a programmer when support for a new use case is added to the system.
Benjamin: I already know that you prefer commands.
Johnny: Yeah, bear with me if only for the sake of seeing how commands can be used here. I am sure you could figure out the Facade option by yourself.
Benjamin: What do I type?
Johnny: Let’s assume we have this command and then let’s think about what we want our TicketOffice to do with it.
Benjamin: We want the TicketOffice to execute the command, obviously..?
Johnny: Right, let’s write this in a form of expectation.
Benjamin: Ok, I’d write something like this:
1 reservationCommand.Received(1).Execute(reservationInProgress);
I already passed the reservationInProgress as the command will need to fill it.
Johnny: Wait, it so happens that I prefer another way of passing this reservationInProgress to the reservationCommand. Please, for now, make the Execute() method parameterless.
Benjamin: As you wish, but I thought this would be a good place to pass it.
Johnny: It might look like it, but typically, I want my commands to have parameterless execution methods. This way I can compose them more freely, using patterns such as decorator8.
Benjamin: As you wish. I reverted the last change and now the THEN section looks like this:
1 //THEN
2 reservationCommand.Received(1).Execute();
3 Assert.Equal(expectedReservationDto, reservationDto);
and it doesn’t compile of course. reservationCommand doesn’t exist, so I need to introduce it. Of course, the type of this variable doesn’t exist as well – I need to pretend it does. And, I already know it should be a mock since I specify that it should have received a call to its Execute() method.
Johnny: (nods)
Benjamin: In the GIVEN section, I’ll add the reservationCommand as a mock:
1 var reservationCommand = Substitute.For<ReservationCommand>();
For the sake of this line, I pretend that I have an interface called ReservationCommand, and now I need to create it to make the code compile.
1 public interface ReservationCommand
2 {
3
4 }
but that’s not enough, because in the Statement, I expect to receive a call to an Execute() method on the command and there’s no such method. I can fix it by adding it on the command interface:
1 public interface ReservationCommand
2 {
3 void Execute();
4 }
and now everything compiles again.
Introducing a command factory collaborator
Johnny: Sure, now we need to figure out how to pass this command to the TicketOffice. As we discussed, by nature, a command object represents, well, an issued command, so it cannot be created once in the composition root and then passed to the constructor, because then:
- it would essentially become a facade,
- we would need to pass the
reservationInProgressto theExecute()method which we wanted to avoid.
Benjamin: Wait, don’t tell me… you want to add another factory here?
Johnny: Yes, that’s what I would like to do.
Benjamin: But… that’s the second factory in a single Statement. Aren’t we, like, overdoing it a little?
Johnny: I understand why you feel that way. Still, this is a consequence of my design choice. We wouldn’t need a command factory if we went with a facade. In simple apps, I just use a facade and do away with this dilemma. I could also drop the use of the collecting parameter pattern and then I wouldn’t need a factory for reservations in progress, but that would mean I would not resolve the command-query separation principle violation and would need to push this violation further into my code. To cheer you up, this is an entry point for a use case where we need to wrap some things in abstractions, so I don’t expect this many factories in the rest of the code. I treat this part as a sort of adapting layer which protects me from everything imposed by outside of my application logic which I don’t want to deal with inside of my application logic.
Benjamin: I will need to trust you on that. I hope it makes things easier later because for now… ugh…
Johnny: Let’s introduce the factory mock. Of course, before I define it, I want to use it first in the Statement to feel a need for it. I will expand the GIVEN section with an assumption that a factory, asked for a command, returns out reservationCommand:
1 //GIVEN
2 ...
3 commandFactory.CreateNewReservationCommand(requestDto, reservationInProgress)
4 .Returns(reservationCommand);
This doesn’t compile because we have no commandFactory yet.
Benjamin: Oh, I can see that the factory’s CreateNewReservationCommand() is where you decided to pass the reservationInProgress that I wanted to pass to the Execute() method earlier. Clever. By leaving the command’s Execute() method parameterless, you made it more abstract and made the interface decoupled from any particular argument types. On the other hand, the command is created in the same scope it is used, so there is literally no issue with passing all the parameters through the factory method.
Johnny: That’s right. We now know we need a factory, plus that it needs to be a mock since we configure it to return a command when it is asked for one. So let’s declare the factory, pretending we have an interface for it:
1 //GIVEN
2 ...
3 var commandFactory = Substitute.For<CommandFactory>();
4 ...
5 commandFactory.CreateNewReservationCommand(requestDto, reservationInProgress)
6 .Returns(reservationCommand);
Benjamin: …and the CommandFactory interface doesn’t exist, so let’s create it:
1 public interface CommandFactory
2 {
3
4 }
and let’s add the missing CreateNewReservationCommand method:
1 public interface CommandFactory
2 {
3 ReservationCommand CreateNewReservationCommand(
4 ReservationRequestDto requestDto,
5 ReservationInProgress reservationInProgress);
6 }
Benjamin: Now the code compiles and looks like this:
1 [Fact]
2 public void ShouldXXXXX() //TODO better name
3 {
4 //WHEN
5 var requestDto = Any.Instance<ReservationRequestDto>();
6 var commandFactory = Substitute.For<CommandFactory>();
7 var reservationInProgressFactory
8 = Substitute.For<ReservationInProgressFactory>();
9 var ticketOffice = new TicketOffice(reservationInProgressFactory);
10 var reservationInProgress = Substitute.For<ReservationInProgress>();
11 var expectedReservationDto = Any.Instance<ReservationDto>();
12 var reservationCommand = Substitute.For<ReservationCommand>();
13
14 commandFactory.CreateNewReservationCommand(requestDto, reservationInProgress)
15 .Returns(reservationCommand);
16 reservationInProgressFactory.FreshInstance().Returns(reservationInProgress);
17 reservationInProgress.ToDto().Returns(expectedReservationDto);
18
19 //WHEN
20 var reservationDto = ticketOffice.MakeReservation(requestDto);
21
22 //THEN
23 Assert.Equal(expectedReservationDto, reservationDto);
24 reservationCommand.Received(1).Execute();
25 }
Benjamin: I can see that the command factory is not passed anywhere from the Statement - the TicketOffice doesn’t know about it.
Johnny: Yes, and, lucky us, a factory is something that can have the same lifetime scope as the TicketOffice since to create the factory, we don’t need to know anything about a request for reservation. This is why we can safely pass it through the constructor of TicketOffice. Which means that these two lines:
1 var commandFactory = Substitute.For<CommandFactory>();
2 var ticketOffice = new TicketOffice(reservationInProgressFactory);
will now look like this:
1 var commandFactory = Substitute.For<CommandFactory>();
2 var ticketOffice = new TicketOffice(
3 reservationInProgressFactory, commandFactory);
A constructor with such a signature does not exist, so to make this compile, we need to add a parameter of type CommandFactory to the constructor:
1 public TicketOffice(
2 ReservationInProgressFactory reservationInProgressFactory,
3 CommandFactory commandFactory)
4 {
5
6 }
which forces us to add a parameter to our composition root. So this part of the composition root:
1 new TicketOffice(
2 new TodoReservationInProgressFactory()) //TODO change the name
becomes:
1 new TicketOffice(
2 new TodoReservationInProgressFactory(), //TODO change the name
3 new TicketOfficeCommandFactory())
which in turn forces us to create the TicketOfficeCommandFactory class:
1 public class TicketOfficeCommandFactory : CommandFactory
2 {
3 public ReservationCommand CreateNewReservationCommand(
4 ReservationRequestDto requestDto,
5 ReservationInProgress reservationInProgress)
6 {
7 throw new NotImplementedException();
8 }
9 }
Benjamin: Hey, this time you gave the class a better name than the previous factory which was called TodoReservationInProgressFactory. Why didn’t you want to leave it for later this time?
Johnny: This time, I think I have a better idea of how to name this class. Typically, I name concrete classes based on something from their implementation and I find the names hard to find when I don’t have this implementation yet. This time I believe I have a name that can last a bit, which is also why I didn’t leave a TODO comment next to this name. Still, further work can invalidate my naming choice, and I will be happy to change the name when the need arises. For now, it should suffice.
Giving the Statement a name
Johnny: Anyway, getting back to the Statement, I think we’ve got it all covered. Let’s just give it a good name. Looking at the assertions:
1 reservationCommand.Received(1).Execute();
2 Assert.Equal(expectedReservationDto, reservationDto);
I think we can say:
1 public void
2 ShouldExecuteReservationCommandAndReturnResponseWhenAskedToMakeReservation()
Benjamin: Just curious… Didn’t you tell me to watch out for the “and” word in Statement names and that it may suggest something is wrong with the scenario.
Johnny: Yes, and in this particular case, there is something wrong - the class TicketOffice violates the command-query separation principle. This is also why the Statement looks so messy. For this class, however, we don’t have a big choice since our framework requires this kind of method signature. That’s also why we are working so hard in this class to introduce the collecting parameter and protect the rest of the design from the violation.
Benjamin: I hope the future Statements will be easier to write than this one.
Johnny: Me too.
Making the Statement true - the first assertion
Johnny: Let’s take a look at the code of the Statement:
1 [Fact] public void
2 ShouldExecuteReservationCommandAndReturnResponseWhenAskedToMakeReservation()
3 {
4 //GIVEN
5 var requestDto = Any.Instance<ReservationRequestDto>();
6 var commandFactory = Substitute.For<CommandFactory>();
7 var reservationInProgressFactory
8 = Substitute.For<ReservationInProgressFactory>();
9 var reservationInProgress = Substitute.For<ReservationInProgress>();
10 var expectedReservationDto = Any.Instance<ReservationDto>();
11 var reservationCommand = Substitute.For<ReservationCommand>();
12
13 var ticketOffice = new TicketOffice(
14 reservationInProgressFactory,
15 commandFactory);
16
17 reservationInProgressFactory.FreshInstance()
18 .Returns(reservationInProgress);
19 commandFactory.CreateNewReservationCommand(requestDto, reservationInProgress)
20 .Returns(reservationCommand);
21 reservationInProgress.ToDto()
22 .Returns(expectedReservationDto);
23
24 //WHEN
25 var reservationDto = ticketOffice.MakeReservation(requestDto);
26
27 //THEN
28 Assert.Equal(expectedReservationDto, reservationDto);
29 reservationCommand.Received(1).Execute();
30 }
Johnny: I think it is complete, but we won’t know that until we see the assertions failing and then passing. For now, the implementation of the MakeReservation() method throws an exception and this exception makes our Statement stop at the WHEN stage, not even getting to the assertions.
Benjamin: But I can’t just put the right implementation in yet, right? This is what you have always told me.
Johnny: Yes, ideally, we should see the assertion errors to gain confidence that the Statement can turn false when the expected behavior is not in place.
Benjamin: This can only mean one thing – return null from the TicketOffice instead of throwing the exception. Right?
Johnny: Yes, let me do it. I’ll just change this code:
1 public ReservationDto MakeReservation(ReservationRequestDto requestDto)
2 {
3 throw new NotImplementedException("Not implemented");
4 }
to:
1 public ReservationDto MakeReservation(ReservationRequestDto requestDto)
2 {
3 return null;
4 }
Now I can see that the first assertion:
1 Assert.Equal(expectedReservationDto, reservationDto);
is failing, because it expects an expectedReservationDto from the reservationInProgress but the reservationInProgress itself can only be received from the factory. The relevant lines in the Statement that say this are:
1 reservationInProgressFactory.FreshInstance()
2 .Returns(reservationInProgress);
3 reservationInProgress.ToDto()
4 .Returns(expectedReservationDto);
Let’s just implement the part that is required to pass this first assertion. To do this, I will need to create a field in the TicketOffice class for the factory and initialize it with one of the constructor parameters. So this code:
1 public TicketOffice(
2 ReservationInProgressFactory reservationInProgressFactory,
3 CommandFactory commandFactory)
4 {
5
6 }
becomes:
1 private readonly ReservationInProgressFactory _reservationInProgressFactory;
2
3 public TicketOffice(
4 ReservationInProgressFactory reservationInProgressFactory,
5 CommandFactory commandFactory)
6 {
7 _reservationInProgressFactory = reservationInProgressFactory;
8 }
Benjamin: Couldn’t you just introduce the other field as well?
Johnny: Yes, I could and I usually do that. But since we are training, I want to show you that we will be forced to do so anyway to make the second assertion pass.
Benjamin: Go on.
Johnny: Now I have to modify the MakeReservation() method by adding the following code that creates the reservation in progress and makes it return a DTO:
1 var reservationInProgress = _reservationInProgressFactory.FreshInstance();
2 return reservationInProgress.ToDto();
Benjamin: …and the first assertion passes.
Johnny: Yes, and this is clearly not enough. If you look at the production code, we are not doing anything with the ReservationRequestDto instance. Thankfully, we have the second assertion:
1 reservationCommand.Received(1).Execute();
and this one fails. To make it pass, We need to create a command and execute it.
Making the Statement true – the second assertion
Benjamin: Wait, why are you calling this an “assertion”? There isn’t a word “assert” anywhere in this line. Shouldn’t we just call it “mock verification” or something like that?
Johnny: I’m OK with “mock verification”, however, I consider it correct to call it an assertion as well, because, in essence, that’s what it is – a check that throws an exception when a condition is not met.
Benjamin: OK, if that’s how you put it…
Johnny: anyway, we still need this assertion to pass.
Benjamin: Let me do this. So to create a command, we need the command factory. This factory is already passed to TicketOffice via its constructor, so we need to assign it to a field because as of now, the constructor looks like this:
1 public TicketOffice(
2 ReservationInProgressFactory reservationInProgressFactory,
3 CommandFactory commandFactory)
4 {
5 _reservationInProgressFactory = reservationInProgressFactory;
6 }
After adding the assignment, the code looks like this:
1 private readonly CommandFactory _commandFactory;
2
3 public TicketOffice(
4 ReservationInProgressFactory reservationInProgressFactory,
5 CommandFactory commandFactory)
6 {
7 _reservationInProgressFactory = reservationInProgressFactory;
8 _commandFactory = commandFactory;
9 }
and now in the MakeReservation() method, I can ask the factory to create a command:
1 var reservationInProgress = _reservationInProgressFactory.FreshInstance();
2 var reservationCommand = _commandFactory.CreateNewReservationCommand(
3 requestDto, reservationInProgress);
4 return reservationInProgress.ToDto();
But this code is not complete yet – I still need to execute the created command like this:
1 var reservationInProgress = _reservationInProgressFactory.FreshInstance();
2 var reservationCommand = _commandFactory.CreateNewReservationCommand(
3 requestDto, reservationInProgress);
4 reservationCommand.Execute();
5 return reservationInProgress.ToDto();
Johnny: Great! Now the Statement is true.
Benjamin: Wow, this isn’t a lot of code for such a big Statement that we wrote.
Johnny: The real complexity is not even in the lines of code, but the number of dependencies that we had to drag inside. Note that we have two factories in here. Each factory is a dependency and it creates another dependency. This is better visible in the Statement than in the production method and this is why I find it a good idea to pay close attention to how a Statement is growing and using it as a feedback mechanism for the quality of my design. For this particular class, the design issue we observe in the Statement can’t be helped a lot since, as I mentioned, this is the boundary where we need to wrap things in abstractions.
Benjamin: You’ll have to explain this bit about design quality a bit more later.
Johnny: Sure. Tea?
Benjamin: Coffee.
Johnny: Whatever, let’s go.
Summary
This is how Johnny and Benjamin accomplished their first Statement using TDD and mock with an outside-in design approach. What will follow in the next chapter is a small retrospective with comments on what these guys did. One thing I’d like to mention now is that the outside-in approach does not rely solely on unit-level Statements, so what you saw here is not the full picture. We will get to that soon.
Test-driving at the input boundary – a retrospective
A lot of things happened in the last chapter and I feel some of them deserve a deeper dive. The purpose of this chapter is to do a small retrospective of what Johnny and Benjamin did when test-driving a controller-type class for the train reservation system.
Outside-in development
Johnny and Benjamin started their development almost entirely at the peripherals of the system, at the beginning of the flow of control. This is typical of the outside-in approach to software development. It took me a while to get used to it. To illustrate it, let’s consider a system of three classes, where Object3 depends on Object2 and Object2 depends on Object1:
1 Object3 -> Object2 -> Object1
Before adopting the outside-in approach, my habit was to start with the objects at the end of the dependency chain (which would typically be at the end of the control flow as well) because I had everything I needed to run and check them. Looking at the graph above, I could develop and run the logic in Object1 because it did not require dependencies. Then, I could develop Object2 because it depended on Object1 that I already created and I could then do the same with Object3 because it depended only on Object2 that I already had. At any given time, I could run everything I created up to that point.
The outside-in approach contradicted this habit of mine, because the objects I had to start with were the ones that had to have dependencies and these dependencies did not exist yet. Looking at the example above, I would need to start with Object3, which could not be instantiated without Object2, which, in turn, could not be instantiated without Object1.
“If this feels difficult, then why bother?” you might ask. My reasons are:
- By starting from the inputs and going inside, I allow my interfaces and protocols to be shaped by use cases rather than by the underlying technology. That does not mean I can always ignore the technology stuff, but I consider the use case logic to be the main driver. This way, my protocols tend to be more abstract which in turn enforces higher composability.
- Every line of code I introduce is there because the use case needs it. Every method, every interface, and class exists because there already exists someone who needs it to perform its obligations. This way, I ensure I only implement the stuff that’s needed and that it is shaped the way the users find it comfortable to use. Before adpoting this approach, I would start from the inside of the system and I would design classes by guessing how they would be used and I would later often regret these guesses, because of the rework and complexity they would create.
For me, these pros outweigh the cons. Moreover, I found I can mitigate the uncomfortable feeling of starting from the inputs (“there is nothing I can fully run”) with the following practices:
- Using TDD with mocks – TDD encourages every little piece of code to be executed well before the whole task completion. Mock objects serve as the first collaborators that allow this execution to happen. To take advantage of mocks, at least in C#, I need to use interfaces more liberally than in a typical development approach. A property of interfaces is that they typically don’t contain implementation9, so they can be used to cut the dependency chain. In other words, whereas without interfaces, I would need all three:
Object1,Object2andObject3to instantiate and useObject3, I could alternatively introduce an interface thatObject3would depend on andObject2would implement. This would allow me to useObject3before its concrete collaborators exist, simply by supplying a mock object as its dependency. - Slicing the scope of work into smaller vertical parts (e.g. scenarios, stories, etc.) that can be implemented faster than a full-blown feature. We had a taste of this in action when Johnny and Benjamin were developing the calculator in one of the first chapters of this book.
- Not starting with a unit-level Statement, but instead, writing on a higher-level first (e.g. end-to-end or against another architectural boundary). I could then make this bigger Statement work, then refactor the initial objects out of this small piece of working code. Only after having this initial structure in place would I start using unit-level Statements with mocks. This approach is what I will be aiming at eventually, but for this and several furter chapters, I want to focus on the mocks and object-oriented design, so I left this part for later.
Workflow specification
The Statement about the controller is an example of what Amir Kolsky and Scott Bain call a workflow Statement10. This kind of Statement describes how a specified unit of behavior (in our case, an object) interacts with other units by sending messages and receiving answers. In Statements specifying workflow, we document the intended purpose and behaviors of the specified class in terms of its interaction with other roles in the system. We use mock objects to play these roles by specifying the return values of some methods and asserting that other methods are called.
For example, in the Statement Johnny and Benjamin wrote in the last chapter, they described how a command factory reacts when asked for a new command and they also asserted on the call to the command’s Execute() method. That was a description of a workflow.
Should I verify that the factory got called?
You might have noticed in the same Statement that some interactions were verified (using the .Received() syntax) while some were only set up to return something. An example of the latter is a factory, e.g.
1 reservationInProgressFactory.FreshInstance().Returns(reservationInProgress);`
You may question why Johnny and Benjamin did not write something like
reservationInProgressFactory.Received().FreshInstance() at the end.
One reason is that a factory resembles a function – it is not supposed to have any visible side-effects. As such, calling the factory is not a goal of the behavior I specify – it will always be only a mean to an end. For example, the goal of the behavior Johnny and Benjamin specified was to execute the command and return its result. The factory was brought to existence to make getting there easier.
Also, Johnny and Benjamin allowed the factory to be called many times in the implementation without altering the expected behavior in the Statement. For example, if the code of the MakeReservation() method they were test-driving did not look like this:
1 var reservationInProgress = _reservationInProgressFactory.FreshInstance();
2 var reservationCommand = _commandFactory.CreateNewReservationCommand(
3 requestDto, reservationInProgress);
4 reservationCommand.Execute();
5 return reservationInProgress.ToDto();
but like this:
1 // Repeated multiple times:
2 var reservationInProgress = _reservationInProgressFactory.FreshInstance();
3 reservationInProgress = _reservationInProgressFactory.FreshInstance();
4 reservationInProgress = _reservationInProgressFactory.FreshInstance();
5 reservationInProgress = _reservationInProgressFactory.FreshInstance();
6
7 var reservationCommand = _commandFactory.CreateNewReservationCommand(
8 requestDto, reservationInProgress);
9 reservationCommand.Execute();
10 return reservationInProgress.ToDto();
then the behavior of this method would still be correct. Sure, it would do some needless work, but when writing Statements, I care about externally visible behavior, not lines of production code. I leave more freedom to the implementation and try not to overspecify.
On the other hand, consider the command – it is supposed to have a side effect, because I expect it to alter some kind of reservation registry in the end. So if I sent the Execute() message more than once like this:
1 var reservationInProgress = _reservationInProgressFactory.FreshInstance();
2 var reservationCommand = _commandFactory.CreateNewReservationCommand(
3 requestDto, reservationInProgress);
4 reservationCommand.Execute();
5 reservationCommand.Execute();
6 reservationCommand.Execute();
7 reservationCommand.Execute();
8 reservationCommand.Execute();
9 return reservationInProgress.ToDto();
then it could possibly alter the behavior – maybe by reserving more seats than the user requested, maybe by throwing an error from the second Execute()… This is why I want to strictly specify how many times the Execute() message should be sent:
1 reservationCommand.Received(1).Execute();
The approach to specifying functions and side-effects I described above is what Steve Freeman and Nat Pryce call “Allow queries; expect commands”11. According to their terminology, the factory is a “query” – side-effect-free logic returning some kind of result. The ReservationCommand, on the other side, is a “command” - not producing any kind of result, but causing a side-effect like a state change or an I/O operation.
Data Transfer Objects and TDD
While looking at the initial data structures, Johnny and Benjamin called them Data Transfer Objects.
A Data Transfer Object is a pattern to describe objects responsible for exchanging information between processes12. As processes cannot really exchange objects, the purpose of DTOs is to be serialized into some kind of data format and then transferred in that form to another process which deserializes the data. So, we can have DTOs representing output that our process sends out and DTOs representing input that our process receives.
As you might have seen, DTOs are typically just data structures. That may come as surprising, because for several chapters now, I have repeatedly stressed how I prefer to bundle data and behavior together. Isn’t this breaking all the principles that I mentioned?
My response to this would be that exchanging information between processes is where the mentioned principles do not apply and that there are some good reasons why data is exchanged between processes.
- It is easier to exchange data than to exchange behavior. If I wanted to send behavior to another process, I would have to send it as data anyway, e.g. in a form of source code. In such a case, the other side would need to interpret the source code, provide all the dependencies, etc. which could be cumbersome and strongly couple implementations of both processes.
- Agreeing on a simple data format makes creating and interpreting the data in different programming languages easier.
- Many times, the boundaries between processes are designed as functional boundaries at the same time. In other words, even if one process sends some data to another, both of these processes would not want to execute the same behavior on the data.
These are some of the reasons why processes send data to each other. And when they do, they typically bundle the data into bigger structures for consistency and performance reasons.
DTOs vs value objects
While DTOs, similarly to value objects, carry and represent data, their purpose and design constraints are different.
- Values have value semantics, i.e. they can be compared based on their content. This is one of the core principles of their design. DTOs don’t need to have value semantics (if I add value semantics to DTOs, I do it because I find it convenient for some reason, not because it’s a part of the domain model).
- DTOs must be easily serializable and deserializable from some kind of data exchange format (e.g. JSON or XML).
- Values may contain behavior, even quite complex (an example of this would be the
Replace()method of theStringclass), while DTOs typically contain no behavior at all. - Despite the previous point, DTOs may contain value objects, as long as these value objects can be reliably serialized and deserialized without loss of information. Value objects don’t contain DTOs.
- Values represent atomic and well-defined concepts (like text, date, money), while DTOs mostly function as bundles of data.
DTOs and mocks
As we observed in the example of Johnny and Benjamin writing their first Statement, they did not mock DTOs. It’s a general rule – a DTO is a piece of data, it does not represent an implementation of an abstract protocol nor does it benefit from polymorphism the way objects do. Also, it is typically far easier to create an instance of a DTO than to mock it. Imagine we have the following DTO:
1 public class LoginDto
2 {
3 public LoginDto(string login, string password)
4 {
5 Login = login;
6 Password = password;
7 }
8
9 public string Login { get; }
10 public string Password { get;}
11 }
An instance of this class can be created by typing:
1 var loginDto = new LoginDto("James", "007");
If we were to create a mock, we would probably need to extract an interface:
1 public class ILoginDto
2 {
3 public string Login { get; }
4 public string Password { get; }
5 }
and then write something like this in our Statement:
1 var loginDto = Substitute.For<ILoginDto>();
2 loginDto.Login.Returns("James");
3 loginDto.Password.Returns("Bond");
Not only is this more verbose, it also does not buy us anything. Hence my advice:
Creating DTOs in Statements
As DTOs tend to bundle data, creating them for specific Statements might be a chore as there might sometimes be several fields we would need to initialize in each Statement. How do I approach creating instances of DTOs to avoid this? I summarized my advice on dealing with this as the priority-ordered list below:
1. Limit the reach of your DTOs in the production code
As a rule of thumb, the fewer types and methods know about them, the better. DTOs represent an external application contract. They are also constrained by some rules mentioned earlier (like the ease of serialization), so they cannot evolve the same way normal objects do. Thus, I try to limit the number of objects that know about DTOs in my application to a necessary minimum. I use one of the two strategies: wrapping or mapping.
When wrapping, I have another object that holds a reference to the DTO and then all the other pieces of logic interact with this wrapping object instead of directly with a DTO:
1 var user = new User(userDto);
2 //...
3 user.Assign(resource);
I consider this approach simpler but more limited. I find that it encourages me to shape the domain objects similarly to how the DTOs are designed (because one object wraps one DTO). I usually start with this approach when the external contract is close enough to my domain model and move to the other strategy – mapping – when the relationship starts getting more complex.
When mapping, I unpack the DTO and pass specific parts into my domain objects:
1 var user = new User(
2 userDto.Name,
3 userDto.Surname,
4 new Address(
5 userDto.City,
6 userDto.Street));
7 //...
8 user.Assign(resource);
This approach requires me to rewrite data into new objects field by field, but in exchange, it leaves me more room to shape my domain objects independent of the DTO structure13. In the example above, I was able to introduce an Address abstraction even though the DTO does not have an explicit field containing the address.
How does all of this help me avoid the tediousness of creating DTOs? Well, the fewer objects and methods know about a DTO, the fewer Statements will need to know about it as well, which leads to fewer places where I need to create and initialize one.
2. Use constrained non-determinism if you don’t need specific data
In many Statements where I need to create DTOs, the specific values held inside it don’t matter to me. I care only about some data being there. For situations like this, I like using constrained non-determinism. I can just create an anonymous instance and use it, which I find easier than assigning field by field.
As an example, look at the following line from the Statement Johnny and Benjamin wrote in the last chapter:
1 var requestDto = Any.Instance<ReservationRequestDto>();
In that Statement, they did not need to care about the exact values held by the DTO, so they just created an anonymous instance. In this particular case, using constrained non-determinism not only simplified the creation of the DTO, but it even allowed them to completely decouple the Statement from the DTO’s structure.
3. Use patterns such as factory methods or builders
When all else fails, I use factory methods and test data builders to ease the pain of creating DTOs to hide away the complexity and provide some good default values for the parts I don’t care about.
A factory method can be useful if there is a single distinguishing factor about the particular instance that I want to create. For example:
1 public UserDto AnyUserWith(params Privilege[] privileges)
2 {
3 var dto = Any.Instance<UserDto>()
4 .WithPropertyValue(user => user.Privileges, privileges);
5 return dto;
6 }
This method creates any user with a particular set of privileges. Note that I utilized constrained non-determinism as well in this method, which spared me some initialization code. If this is not possible, I try to come up with some sort of “safe default” values for each of the fields.
I like factory methods, but the more flexibility I need, the more I gravitate towards test data builders14.
A test data builder is a special object that allows me to create an object with some default values, but allows me to customize the default recipe according to which the object is created. It leaves me much more flexibility with how I set up my DTOs. The typical syntax for using a builder that you can find on the internet15 looks like this:
1 var user = new UserBuilder().WithName("Johnny").WithAge("43").Build();
Note that the value for each field is configured separately. Typically, the builder holds some kind of default values for the fields I don’t specify:
1 //some safe default age will be used when not specified:
2 var user = new UserBuilder().WithName("Johnny").Build();
I am not showing an example implementation on purpose, because one of the further chapters will include a longer discussion of test data builders.
Using a ReservationInProgress
A controversial point of the design in the last chapter might be the usage of a ReservationInProgress class. The core idea of this abstraction is to collect the data needed to produce a result. To introduce this object, we needed a separate factory, which made the design more complex. Thus, some questions might pop into your mind:
- What exactly is
ReservationInProgress? - Is the
ReservationInProgressreally necessary and if not, what are the alternatives? - Is a separate factory for
ReservationInProgressneeded?
Let’s try answering them.
What exactly is ReservationInProgress?
As mentioned earlier, the intent for this object is to collect information about processing a command, so that the issuer of the command (in our case, a controller object) can act on that information (e.g. use it to create a response) when processing finishes. Speaking in patterns language, this is an implementation of a Collecting Parameter pattern16.
There is something I often do, but I did not put in the example for the sake of simplicity. When I implement a collecting parameter, I typically make it implement two interfaces – one more narrow and the other one – wider. Let me show them to you:
1 public interface ReservationInProgress
2 {
3 void Success(SomeData data);
4 //... methods for reporting other events
5 }
6
7 public interface ReservationInProgressMakingReservationDto
8 : ReservationInProgress
9 {
10 ReservationDto ToDto();
11 }
The whole point is that only the issuer of the command can see the wider interface
(ReservationInProgressMakingReservationDto) and when it passes this interface down the call chain, the next object only sees the methods for reporting events (ReservationInProgress). This way, the wider interface can even be tied to a specific technology, as long as the narrower one is not. For example, If I needed a JSON string response, I might do something like this:
1 public interface ReservationInProgressMakingReservationJson
2 : ReservationInProgress
3 {
4 string ToJsonString();
5 }
and only the command issuer (in our case, the controller object) would know about that. The rest of the classes using the narrower interface would interact with it happily without ever knowing that it is meant to produce a JSON output.
Is ReservationInProgress necessary?
In short – no, although I find it useful. There are at least several alternative designs.
First of all, we might decide to return from the command’s Execute() method. Then, the command would look like this:
1 public interface ReservationCommand
2 {
3 public ReservationDto Execute();
4 }
This would do the job for the task at hand but would make the ReservationCommand break the command-query separation principle, which I like to uphold as much as I can. Also, the ReservationCommand interface would become much less reusable. If our application was to support different commands, each returning a different type of result, we could not have a single interface for all of them. This, in turn, would make it more difficult to decorate the commands using the decorator pattern. We might try to fix this by making the command generic:
1 public interface ReservationCommand<T>
2 {
3 public T Execute();
4 }
but this still leaves a distinction between void and non-void commands (which some people resolve by parameterizing would-be void commands with bool and always returning true at the end).
The second option to avoid a collecting parameter would be to just let the command execute and then obtain the result by querying the state that was modified by the command (similarly to a command, a query may be a separate object). The code of the MakeReservation() would look somewhat like this:
1 var reservationId = _idGenerator.GenerateId();
2 var reservationCommand = _factory.CreateNewReservationCommand(
3 requestDto, reservationId);
4 var reservationQuery = _factory.CreateReservationQuery(reservationId);
5
6 reservationCommand.Execute();
7 return reservationQuery.Make();
Note that in this case, there is nothing like “result in progress”, but on the other hand, we need to generate the id for the command, as the query must use the same id. This approach might appeal to you if:
- You don’t mind that if you store your changes in a database or an external service, your logic might need to call it twice – once during the command, and again during the query.
- The state that is modified by the command is queryable (i.e. a potential destination API for data allows executing both commands and queries on the data). It’s not always a given.
There are more options, but I’d like to stop here as this is not the main concern of this book.
Do we need a separate factory for ReservationInProgress?
This question can be broken into two parts:
- Can we use the same factory as for commands?
- Do we need a factory at all?
The answer to the first one is: it depends on what the ReservationInProgress is coupled to. In this specific example, it is just creating a DTO to be returned to the client. In such a case, it does not necessarily need any knowledge of the framework that is being used to run the application. This lack of coupling to the framework would allow me to place creating ReservationInProgress in the same factory. However, if this class needed to decide e.g. HTTP status codes or create responses required by a specific framework or in a specified format (e.g. JSON or XML), then I would opt, as Johnny and Benjamin did, for separating it from the command factory. This is because the command factory belongs to the world of application logic and I want my application logic to be independent of the framework, the transport protocols, and the payload formats that I use.
The answer to the second question (whether we need a factory at all) is: it depends whether you care about specifying the controller behavior on the unit level. If yes, then it may be handy to have a factory to control the creation of ReservationInProgress. If you don’t want to (e.g. you drive this logic with higher-level Specification, which we will talk about in one of the next parts), then you can decide to just create the object inside the controller method or even make the controller itself implement the ReservationInProgress interface (though if you did that, you would need to make sure a single controller instance is not shared between multiple requests, as it would contain mutable state).
Should I specify the controller behavior on the unit level?
As I mentioned, it’s up to you. As controllers often serve as adapters between a framework and the application logic, they are constrained by the framework and so there is not that much value in driving their design with unit-level Statements. It might be more accurate to say that these Statements are closer to integration Specification because they often describe how the application is integrated into a framework.
An alternative to specifying the integration on the unit level could be driving it with higher-level Statements (which I will be talking about in detail in the coming parts of this book). For example, I might write a Statement describing how my application works end-to-end, then write a controller (or another framework adapter) code without a dedicated unit-level Statement for it and then use unit-level Statements to drive the application logic. When the end-to-end Statement passes, it means that the integration that my controller was meant to provide, works.
There are some preconditions for this approach to work, but I will cover them when talking about higher-level Statements in the further parts of this book.
Interface discovery and the sources of abstractions
As promised, the earlier chapter included some interface discovery, even though it wasn’t a typical way of applying this approach. This is because, as I mentioned, the goal of the Statement was to describe integration between the framework and the application code. Still, writing the Statement allowed Johnny and Benjamin to discover what their controller needs from the application to pass all the necessary data to it and to get the result without violating the command-query separation principle.
The different abstractions Johnny pulled into existence were driven by:
- his need to convert to a different programming style (command-query separation principle, “tell, don’t ask”…),
- his knowledge about design patterns,
- his experience with similar problems.
Had he lacked these things, he would have probably written the simplest thing that would come to his mind and then changed the design after learning more.
Do I need all of this to do TDD?
The last chapter might have left you confused. If you are wondering whether you need to use controllers, collecting parameters, commands and factories in order to do TDD, than my answer is not necessarily. Moreover, there are many debates on the internet whether these particular patterns are the right ones to use and even I don’t like using controllers with problems such as this one (although I used it because this is what many web frameworks offer as a default choice).
I needed all of this stuff to create an example that resembles a real-life problem. The most important lesson though is that, while making the design decisions was up to Johnny and Benjamin’s knowledge and experience, writing the Statement code before the implementation informed these design decisions, helping them distribute the responsibilities across collaborating objects.
What’s next?
This chapter hopefully connected all the missing dots of the last one. The next chapter will focus on test-driving object creation.
Test-driving object creation
In this chapter, we join Johnny and Benjamin as they continue their outside-in TDD journey. In this chapter, they will be test-driving a factory.
Johnny: What’s next on our TODO list?
Benjamin: There are two instances of NotImplementedException, both from factories that we discovered when implementing the last Statement.
The first factory is for creating instances of ReservationInProgress – remember? You gave it a bogus name on purpose. Look:
1 public class TodoReservationInProgressFactory : ReservationInProgressFactory
2 {
3 public ReservationInProgress FreshInstance()
4 {
5 throw new NotImplementedException();
6 }
7 }
Johnny: Oh, yeah, that one…
Benjamin: and the second factory is for creating commands – it’s called TicketOfficeCommandFactory:
1 public class TicketOfficeCommandFactory : CommandFactory
2 {
3 public ReservationCommand CreateNewReservationCommand(
4 ReservationRequestDto requestDto,
5 ReservationInProgress reservationInProgress)
6 {
7 throw new NotImplementedException();
8 }
9 }
Johnny: Let’s do this one.
Benjamin: Why this one?
Johnny: No big reason. I just want to dig into the domain logic as soon as possible.
Benjamin: Right. We don’t have a Specification for this class, so let’s create one.
1 public class TicketOfficeCommandFactorySpecification
2 {
3
4 }
Johnny: Go on, I think you will be able to write this Statement mostly without my help.
Benjamin: The beginning looks easy. I’ll add a new Statement which I don’t know yet what I’m going to call.
1 public class TicketOfficeCommandFactorySpecification
2 {
3 [Fact]
4 public void ShouldCreateXXXXXXXXX() //TODO rename
5 {
6 Assert.True(false);
7 }
8 }
Now, as you’ve told me before, I already know what class I am describing – it’s TicketOfficeCommandFactory. It implements an interface that I discovered in the previous Statement, so it already has a method: CreateNewReservationCommand. So in the Statement, I’ll just create an instance of the class and call the method, because why not?
1 public class TicketOfficeCommandFactorySpecification
2 {
3 [Fact]
4 public void ShouldCreateXXXXXXXXX() //TODO rename
5 {
6 //GIVEN
7 var factory = new TicketOfficeCommandFactory();
8
9 //WHEN
10 factory.CreateNewReservationCommand();
11
12 //THEN
13 Assert.True(false);
14 }
15 }
This doesn’t compile. To find out why, I look at the signature of the CreateNewReservationCommand method, and I see that it not only returns a command, but also accepts two arguments. I need to take that into account in the Statement:
1 public class TicketOfficeCommandFactorySpecification
2 {
3 [Fact]
4 public void ShouldCreateXXXXXXXXX() //TODO rename
5 {
6 //GIVEN
7 var factory = new TicketOfficeCommandFactory();
8
9 //WHEN
10 var command = factory.CreateNewReservationCommand(
11 Any.Instance<ReservationRequestDto>(),
12 Any.Instance<ReservationInProgress>());
13
14 //THEN
15 Assert.True(false);
16 }
17 }
I used Any.Instance<>() to generate anonymous instances of both the DTO and the ReservationInProgress.
Johnny: That’s exactly what I’d do. I’d assign them to variables only if I needed to use them somewhere else in the Statement.
Benjamin: The Statement compiles. I still have the assertion to write. What exactly should I assert? The command I’m getting back from the factory has only a void Execute() method. Everything else is hidden.
Johnny: Typically, there isn’t much we can specify about the objects created by factories. In this case, we can only state the expected type of the command.
Benjamin: Wait, isn’t this already in the signature of the factory? Looking at the creation method, I can already see that the returned type is ReservationCommand… wait!
Johnny: I thought you’d notice that. ReservationCommand is an interface. But an object needs to have a concrete type and this type is what we need to specify in this Statement. We don’t have this type yet. We are on the verge of discovering it, and when we do, some new items will be added to our TODO list.
Benjamin: I’ll call the class NewReservationCommand and modify my Statement to assert the type. Also, I think I know now how to name this Statement:
1 public class TicketOfficeCommandFactorySpecification
2 {
3 [Fact]
4 public void ShouldCreateNewReservationCommandWhenRequested()
5 {
6 //GIVEN
7 var factory = new TicketOfficeCommandFactory();
8
9 //WHEN
10 var command = factory.CreateNewReservationCommand(
11 Any.Instance<ReservationRequestDto>(),
12 Any.Instance<ReservationInProgress>());
13
14 //THEN
15 Assert.IsType<NewReservationCommand>(command);
16 }
17 }
Johnny: Now, let’s see – the Statement looks like it’s complete. You did it almost without my help.
Benjamin: Thanks. The code does not compile, though. The NewReservationCommand type does not exist.
Johnny: Let’s introduce it then:
1 public class NewReservationCommand
2 {
3
4 }
Benjamin: Shouldn’t it implement the ReservationCommand interface?
Johnny: We don’t need to do that yet. The compiler only complained that the type doesn’t exist.
Benjamin: The code compiles now, but the Statement is false because the CreateNewReservationCommand method throw a NotImplementedException:
1 public ReservationCommand CreateNewReservationCommand(
2 ReservationRequestDto requestDto,
3 ReservationInProgress reservationInProgress)
4 {
5 throw new NotImplementedException();
6 }
Johnny: Remember what I told you the last time?
Benjamin: Yes, that the Statement needs to be false for the right reason.
Johnny: Exactly. NotImplementedException is not the right reason. I will change the above code to return null:
1 public ReservationCommand CreateNewReservationCommand(
2 ReservationRequestDto requestDto,
3 ReservationInProgress reservationInProgress)
4 {
5 return null;
6 }
Now the Statement is false because this assertion fails:
1 Assert.IsType<NewReservationCommand>(command);
Benjamin: it complains that the command is null.
Johnny: So the right time came to put in the correct implementation:
1 public ReservationCommand CreateNewReservationCommand(
2 ReservationRequestDto requestDto,
3 ReservationInProgress reservationInProgress)
4 {
5 return new NewReservationCommand();
6 }
Benjamin: But this doesn’t compile – the NewReservationCommand doesn’t implement the ReservationCommand interface, I told you this before.
Johnny: and the compiler forces us to implement this interface:
1 public class NewReservationCommand : ReservationCommand
2 {
3
4 }
The code still doesn’t compile, because the interface has an Execute() method we need to implement in the NewReservationCommand. Any implementation will do as the logic of this method is outside the scope of the current Statement. We only need to make the compiler happy. Our IDE can generate the default method body for us. This should do:
1 public class NewReservationCommand : ReservationCommand
2 {
3 public void Execute()
4 {
5 throw new NotImplementedException();
6 }
7 }
The NotImplementedException will be added to our TODO list and we’ll specify its behavior with another Statement later.
Benjamin: Our current Statement seems to be true now. I’m bothered by something, though. What about the arguments that we are passing to the CreateNewReservationCommand method? There are two:
1 public ReservationCommand CreateNewReservationCommand(
2 ReservationRequestDto requestDto, //first argument
3 ReservationInProgress reservationInProgress) //second argument
4 {
5 return new NewReservationCommand();
6 }
and they are both unused. How come?
Johnny: We could’ve use them and pass them to the command. Sometimes I do this though the Statement does not depend on this, so there is no pressure. I chose not to do it this time to show you that we will need to pass them anyway when we specify the behaviors of our command.
Benjamin: let’s say you are right and when we specify the command behavior, we will need to modify the factory to pass these two, e.g. like this:
1 public ReservationCommand CreateNewReservationCommand(
2 ReservationRequestDto requestDto, //first argument
3 ReservationInProgress reservationInProgress) //second argument
4 {
5 return new NewReservationCommand(requestDto, reservationInProgress);
6 }
but no Statement says which values should be used, so I will as well be able to cheat by writing something like:
1 public ReservationCommand CreateNewReservationCommand(
2 ReservationRequestDto requestDto, //first argument
3 ReservationInProgress reservationInProgress) //second argument
4 {
5 return new NewReservationCommand(null, null);
6 }
and all Statements will still be true.
Johnny: That’s right. Though there are some techniques to specify that on unit level, I’d rather rely on higher-level Statements to guarantee we pass the correct values. I will show you how to do it another time.
Benjamin: Thanks, seems I’ll just have to wait.
Johnny: Anyway it looks like we’re done with this Statement, so let’s take a short break.
Benjamin: Sure!
Test-driving object creation – a retrospective
In the last chapter, Johnny and Benjamin specified behavior of a factory, by writing a Specification Statement about object creation. This chapter will hopefully answer some questions you may have about what they did and what you should be doing in similar situations.
Limits of creation specification
Object creation might mean different things, but in the style of design I am describing, it is mostly about creating abstractions. These abstractions usually encapsulate a lot, exposing only narrow interfaces. An example might be the ReservationCommand from our train reservation system – it contains a single method called Execute(), that returns nothing. The only thing Johnny and Benjamin could specify about the created command in the Statement was its concrete type. They could not enforce the factory method arguments being passed into the created instance, so their implementation just left that away. Still, at some point they would need to pass the correct arguments or else the whole logic would not work.
To enforce the Statement to be false when correct parameters are not passed, they could try using techniques like reflection to inspect the created object graph and verify whether it contains the right objects, but that would soon turn the specification into a fragile mirror of the production code. The object composition is a mostly declarative definition of behavior, much the same as HTML is a declarative description of a GUI. We should specify behavior rather than the structure of what makes the behavior possible.
So how do we know whether the command was created exactly as we intended? Can we specify this at all?
I mentioned that the object composition is meant to provide some kind of higher-level behavior – one resulting from how the objects are composed. The higher-level Statements can describe that behavior. For example, when we specify the behavior of the whole component, These Statements will indirectly force us to get the object composition right – both in our factories and at the composition root level.
For now, I’ll leave it at that, and I’ll go over higher-level Specification in the further parts of the book.
Why specify object creation?
So if we rely on the higher-level Specification to enforce the correct object composition, why do we write a unit-level Specification for object creation? The main reason is: progress. Looking back at the Statement Johnny and Benjamin wrote – it forced them to create a class implementing the ReservationCommand interface. This class then needed to implement the interface method in a way that would satisfy the compiler. Just to remind you, it ended up like this:
1 public class NewReservationCommand : ReservationCommand
2 {
3 public void Execute()
4 {
5 throw new NotImplementedException();
6 }
7 }
This way, they got a new class to test-drive, and the NotImplementedException that appeared in the generated Execute method, landed on their TODO list. As their next step, they could pick this TODO item and begin working on it.
So as you can see, a Statement specifying the factory’s behavior, although not perfect as a test, allowed continuing the flow of TDD.
What do we specify in the creational Statements?
Johnny and Benjamin specified what should be the type of the created object. Indirectly, they also specified that the created object should not be a null. Is there anything else we might want to include in a creational specification?
Choice
Sometimes, factories make a decision on what kind of object to return. In such cases, we might want to specify the different choices that the factory can make. For example if a factory looks like this:
1 public class SomeKindOfFactory
2 {
3 public Command CreateFrom(string commandName)
4 {
5 if(commandName == "command1")
6 {
7 return new Command1();
8 }
9 if(commandName == "command2")
10 {
11 return new Command2();
12 }
13 else
14 {
15 throw new InvalidCommandException(commandName);
16 }
17 }
18 }
then we would need three Statements to specify its behavior:
- For the scenario where an instance of
Command1is returned. - For the scenario where an instance of
Command2is returned. - For the scenario where an exception is thrown.
Collection content
Likewise, if our factory needs to return a collection - we need to state what we expect it to contain. Consider the following Statement about a factory that needs to create a sequence of processing steps:
1 [Fact] public void
2 ShouldCreateProcessingStepsInTheRightOrder()
3 {
4 //GIVEN
5 var factory = new ProcessingStepsFactory();
6
7 //WHEN
8 var steps = factory.CreateStepsForNewItemRequest();
9
10 //THEN
11 Assert.Equal(3, steps.Count);
12 Assert.IsType<ValidationStep>(steps[0]);
13 Assert.IsType<ExecutionStep>(steps[1]);
14 Assert.IsType<ReportingStep>(steps[2]);
15 }
It says that the created collection should contain three items in a specific order and states the expected types of those items.
Last but not least, sometimes we specify the creation of value objects
Value object creation
Value objects are typically created using factory methods. Depending on whether the constructor of a such value object is public or not, we can accommodate these factory methods differently in our Specification.
Value object with a public constructor
When a public constructor is available, we can state the expected equality of an object created using a factory method to another object created using the constructor:
1 [Fact] public void
2 ShouldBeEqualToIdWithGroupType()
3 {
4 //GIVEN
5 var groupName = Any.String();
6
7 //WHEN
8 var id = EntityId.ForGroup(groupName);
9
10 //THEN
11 Assert.Equal(new EntityId(groupName, EntityTypes.Group), id);
12 }
Value object with a non-public constructor
When factory methods are the only means of creating value objects, we just use these methods in our Statements and either state the expected data or expected behaviors. For example, the following Statement says that an absolute directory path should become an absolute file path after appending a file name:
1 [Fact]
2 public void ShouldBecomeAnAbsoluteFilePathAfterAppendingFileName()
3 {
4 //GIVEN
5 var dirPath = AbsoluteDirectoryPath.Value("C:\\");
6 var fileName = FileName.Value("file.txt");
7
8 //WHEN
9 AbsoluteFilePath absoluteFilePath = dirPath.Append(fileName);
10
11 //THEN
12 Assert.Equal(
13 AbsoluteDirectoryPath.Value("C:\\file.txt"),
14 absoluteFilePath);
15 }
Note that the Statement creates the values using their respective factory methods. This is inevitable as these methods are the only way the objects can be created.
Validation
The factory methods for value objects may have some additional behavior like validation – we need to specify it as well. For example, the Statement below says that an exception should be thrown when I try to create an absolute file path from a null string:
1 [Fact]
2 public void ShouldNotAllowToBeCreatedWithNullValue()
3 {
4 Assert.Throws<ArgumentNullException>(() => AbsoluteFilePath.Value(null));
5 }
Summary
That was a quick ride through writing creational Statements. Though not thorough as tests, they create needs to new abstractions, allowing us to continue the TDD process. When we start our TDD process from higher-level Statements, we can defer specifying factories and sometimes even avoid it altogether.
Test-driving application logic
Johnny: What’s next on our TODO list?
Benjamin: We have…
- a single reminder to change the name of the factory that creates
ReservationInProgressinstances. - Also, we have two places where a
NotImplementedExceptionsuggests there’s something left to implement.- The first one is the mentioned factory.
- The second one is the
NewReservationCommandclass that we discovered when writing a Statement for the factory.
Johnny: Let’s do the NewReservationCommand. I suspect the complexity we put inside the controller will pay back here and we will be able to test-drive the command logic on our own terms.
Benjamin: Can’t wait to see that. Here’s the current code of the command:
1 public class NewReservationCommand : ReservationCommand
2 {
3 public void Execute()
4 {
5 throw new NotImplementedException();
6 }
7 }
Johnny: So we have the class and method signature. It seems we can use the usual strategy of writing new Statement.
Benjamin: You mean “start by invoking a method if you have one”? I thought of the same. Let me try it.
1 public class NewReservationCommandSpecification
2 {
3 [Fact] public void
4 ShouldXXXXXXXXXXX() //TODO change the name
5 {
6 //GIVEN
7 var command = new NewReservationCommand();
8
9 //WHEN
10 command.Execute();
11
12 //THEN
13 Assert.True(false); //TODO unfinished
14 }
15 }
This is what I could write almost brain-dead. I just took the parts we have and put them in the Statement.
The assertion is still missing - the one we have is merely a placeholder. This is the right time to think what should happen when we execute the command.
Johnny: To come up with that expected behavior, we need to look back to the input data for the whole use case. We passed it to the factory and forgot about it. So just as a reminder - here’s how it looks like:
1 public class ReservationRequestDto
2 {
3 public readonly string TrainId;
4 public readonly uint SeatCount;
5
6 public ReservationRequestDto(string trainId, uint seatCount)
7 {
8 TrainId = trainId;
9 SeatCount = seatCount;
10 }
11 }
The first part is train id – it says on which train we should reserve the seats. So we need to somehow pick a train from the fleet for reservation. Then, on that train, we need to reserve as many seats as the customer requests. The requested seat count is the second part of the user request.
Benjamin: Aren’t we going to update the data in some kind of persistent storage? I doubt that the railways company would want the reservation to disappear on application restart.
Johnny: Yes, we need to act as if there was some kind of persistence.
Given all of above, I can see two new roles in our scenario:
- A fleet - from which we pick the train and where we save our changes
- A train - which is going to handle the reservation logic.
Both of these roles need to be modeled as mocks, because I expect them to play active roles in this scenario.
Let’s expand our Statement with our discoveries.
1 [Fact] public void
2 ShouldReserveSeatsInSpecifiedTrainWhenExecuted()
3 {
4 //GIVEN
5 var command = new NewReservationCommand(fleet);
6
7 fleet.Pick(trainId).Returns(train);
8
9 //WHEN
10 command.Execute();
11
12 //THEN
13 Received.InOrder(() =>
14 {
15 train.ReserveSeats(seatCount);
16 fleet.UpdateInformationAbout(train);
17 };
18 }
Benjamin: I can see there are many things missing here. For instance, we don’t have the train and fleet variables.
Johnny: We created a need for them. I think we can safely introduce them into the Statement now.
1 [Fact] public void
2 ShouldReserveSeatsInSpecifiedTrainWhenExecuted()
3 {
4 //GIVEN
5 var fleet = Substitute.For<TrainFleet>();
6 var train = Substitute.For<ReservableTrain>();
7 var command = new NewReservationCommand(fleet);
8
9 fleet.Pick(trainId).Returns(train);
10
11 //WHEN
12 command.Execute();
13
14 //THEN
15 Received.InOrder(() =>
16 {
17 train.ReserveSeats(seatCount);
18 fleet.UpdateInformationAbout(train);
19 };
20 }
Benjamin: I see two new types: TrainFleet and ReservableTrain.
Johnny: They symbolize the roles we just discovered.
Benjamin: Also, I can see that you used Received.InOrder() from NSubstitute to specify the expected call order.
Johnny: That’s because we need to reserve the seats before we update the information in some kind of storage. If we got the order wrong, the change could be lost.
Benjamin: But something is missing in this Statement. I just looked at the outputs our users expect:
1 public class ReservationDto
2 {
3 public readonly string TrainId;
4 public readonly string ReservationId;
5 public readonly List<TicketDto> PerSeatTickets;
6
7 public ReservationDto(
8 string trainId,
9 List<TicketDto> perSeatTickets,
10 string reservationId)
11 {
12 TrainId = trainId;
13 PerSeatTickets = perSeatTickets;
14 ReservationId = reservationId;
15 }
16 }
That’s a lot of info we need to pass back to the user. How exactly are we going to do that when the train.ReserveSeats(seatCount) call you invented is not expected to return anything?
Johnny: Ah, yes, I almost forgot - we’ve got the ReservationInProgress instance that we passed to the factory, but not yet to the command, right? The ReservationInProgress was invented exactly for this purpose - to gather the information necessary to produce a result of the whole operation. Let me just quickly update the Statement:
1 [Fact] public void
2 ShouldReserveSeatsInSpecifiedTrainWhenExecuted()
3 {
4 //GIVEN
5 var fleet = Substitute.For<TrainFleet>();
6 var train = Substitute.For<ReservableTrain>();
7 var reservationInProgress = Any.Instance<ReservationInProgress>();
8 var command = new NewReservationCommand(fleet, reservationInProgress);
9
10 fleet.Pick(trainId).Returns(train);
11
12 //WHEN
13 command.Execute();
14
15 //THEN
16 Received.InOrder(() =>
17 {
18 train.ReserveSeats(seatCount, reservationInProgress);
19 fleet.UpdateInformationAbout(train);
20 };
21 }
Now the ReserveSeats method accepts reservationInProgress.
Benjamin: Why are you passing the reservationInProgress further to the ReserveSeats method?
Johnny: The command does not have the necessary information to fill the reservationInProgress once the reservation is successful. We need to defer it to the ReservableTrain implementations to further decide the best place to do that.
Benjamin: I see. Looking at the Statement again - we’re missing two more variables – trainId and seatCount – and not only their definitions, but also we don’t pass them to the command at all. They are only present in our assumptions and expectations.
Johnny: Right, let me correct that.
1 [Fact] public void
2 ShouldReserveSeatsInSpecifiedTrainWhenExecuted()
3 {
4 //GIVEN
5 var fleet = Substitute.For<TrainFleet>();
6 var train = Substitute.For<ReservableTrain>();
7 var trainId = Any.String();
8 var seatCount = Any.UnsignedInt();
9 var reservationInProgress = Any.Instance<ReservationInProgress>();
10 var command = new NewReservationCommand(
11 trainId,
12 seatCount,
13 fleet,
14 reservationInProgress);
15
16 fleet.Pick(trainId).Returns(train);
17
18 //WHEN
19 command.Execute();
20
21 //THEN
22 Received.InOrder(() =>
23 {
24 train.ReserveSeats(seatCount, reservationInProgress);
25 fleet.UpdateInformationAbout(train);
26 };
27 }
Benjamin: Why is seatCount a uint?
Johnny: Look it up in the DTO - it’s a uint there. I don’t see the need to redefine that here.
Benjamin: Fine, but what about the trainId - it’s a string. Didn’t you tell me we need to use domain-related value objects for concepts like this?
Johnny: Yes, and we will refactor this string into a value object, especially that we have a requirement that train id comparisons should be case-insensitive. But first, I want to finish this Statement before I go into defining and specifying a new type. Still, we’d best leave a TODO note to get back to it later:
1 var trainId = Any.String(); //TODO extract value object
So far so good, I think we have a complete Statement. Want to take the keyboard?
Benjamin: Thanks. Let’s start implementing it then. First, I will start with these two interfaces:
1 var fleet = Substitute.For<TrainFleet>();
2 var train = Substitute.For<ReservableTrain>();
They don’t exist, so this code doesn’t compile. I can easily fix this by creating the interfaces in the production code:
1 public interface TrainFleet
2 {
3 }
4
5 public interface ReservableTrain
6 {
7 }
Now for this part:
1 var command = new NewReservationCommand(
2 trainId,
3 seatCount,
4 fleet,
5 reservationInProgress);
It doesn’t compile because the command does not accept any constructor parameters yet. Let’s create a fitting constructor, then:
1 public class NewReservationCommand : ReservationCommand
2 {
3 public NewReservationCommand(
4 string trainId,
5 uint seatCount,
6 TrainFleet fleet,
7 ReservationInProgress reservationInProgress)
8 {
9
10 }
11
12 public void Execute()
13 {
14 throw new NotImplementedException();
15 }
16 }
Our Statement can now invoke this constructor, but we broke the TicketOfficeCommandFactory which also creates a NewReservationCommand instance. Look:
1 public ReservationCommand CreateNewReservationCommand(
2 ReservationRequestDto requestDto,
3 ReservationInProgress reservationInProgress)
4 {
5 //Stopped compiling:
6 return new NewReservationCommand();
7 }
Johnny: We need to fix the factory the same way we needed to fix the composition root when test-driving the controller. Let’s see… Here:
1 public ReservationCommand CreateNewReservationCommand(
2 ReservationRequestDto requestDto,
3 ReservationInProgress reservationInProgress)
4 {
5 return new NewReservationCommand(
6 requestDto.TrainId,
7 requestDto.SeatCount,
8 new TodoTrainFleet(), // TODO fix name and scope
9 reservatonInProgress
10 );
11 }
Benjamin: The parameter passing looks straightforward to me except the TodoTrainFleet() – I already know that the name is a placeholder - you already did something like that earlier. But what about the lifetime scope?
Johnny: It’s also a placeholder. For now, I want to make the compiler happy, at the same time keeping existing Statements true and introducing a new class – TodoTrainFleet – that will bring new items to our TODO list.
Benjamin: New TODO items?
Johnny: Yes. Look – the type TodoTrainFleet does not exist yet. I’ll create it now:
1 public class TodoTrainFleet
2 {
3
4 }
This doesn’t match the signature of the command constructor, which expects a TrainFleet, so I need to make TodoTrainFleet implement this interface:
1 public class TodoTrainFleet : TrainFleet
2 {
3
4 }
Now I am forced to implement the methods from the TrainFleet interface. Although this interface doesn’t define any methods yet, we already discovered two in our Statement, so it will shortly need to get them to make the compiler happy. They will both contain code throwing NotImplementedException, which will land on the TODO list.
Benjamin: I see. Anyway, the factory compiles again. We still got this part of the Statement left:
1 fleet.Pick(trainId).Returns(train);
2
3 //WHEN
4 command.Execute();
5
6 //THEN
7 Received.InOrder(() =>
8 {
9 train.ReserveSeats(seatCount, reservationInProgress);
10 fleet.UpdateInformationAbout(train);
11 };
Johnny: That’s just introducing three methods. You can handle it.
Benjamin: Thanks. The first line is fleet.Pick(trainId).Returns(train). I’ll just generate the Pick method using my IDE:
1 public interface TrainFleet
2 {
3 ReservableTrain Pick(string trainId);
4 }
The TrainFleet interface is implemented by the TodoTrainFleet we talked about several minutes ago. It needs to implement the Pick method as well or else it won’t compile:
1 public class TodoTrainFleet : TrainFleet
2 {
3 public ReservableTrain Pick(string trainId)
4 {
5 throw new NotImplementedException();
6 }
7 }
This NotImplementedException will land on our TODO list just as you mentioned. Nice!
Then comes the next line from the Statement: train.ReserveSeats(seatCount, reservationInProgress) and I’ll generate a method signature out of it the same as from the previous line.
1 public interface ReservableTrain
2 {
3 void ReserveSeats(uint seatCount, ReservationInProgress reservationInProgress);
4 }
ReservableTrain interface doesn’t have any implementations so far, so nothing more to do with this method.
The last line: fleet.UpdateInformationAbout(train) which needs to be added to the TrainFleet interface:
1 public interface TrainFleet
2 {
3 ReservableTrain Pick(string trainId);
4 void UpdateInformationAbout(ReservableTrain train);
5 }
Also, we need to define this method in the TodoTrainFleet class:
1 public class TodoTrainFleet : TrainFleet
2 {
3 public ReservableTrain Pick(string trainId)
4 {
5 throw new NotImplementedException();
6 }
7
8 void UpdateInformationAbout(ReservableTrain train)
9 {
10 throw new NotImplementedException();
11 }
12 }
Johnny: This NotImplementedException will be added to the TODO list as well, so we can revisit it later. It looks like the Statement compiles and, as expected, is false, but not for the right reason.
Benjamin: Let me see… yes, a NotImplementedException is thrown from the command’s Execute() method.
Johnny: Let’s get rid of it.
Benjamin: Sure. I removed the throw and the method is empty now:
1 public void Execute()
2 {
3
4 }
The Statement is false now because the expected calls are not matched.
Johnny: Which means we are finally ready to code some behavior into the NewReservationCommand class. First, let’s assign all the constructor parameters to fields – we’re going to need them.
Benjamin: Here:
1 public class NewReservationCommand : ReservationCommand
2 {
3 private readonly string _trainId;
4 private readonly uint _seatCount;
5 private readonly TraingFleet _fleet;
6 private readonly ReservationInProgress _reservationInProgress;
7
8 public NewReservationCommand(
9 string trainId,
10 uint seatCount,
11 TrainFleet fleet,
12 ReservationInProgress reservationInProgress)
13 {
14 _trainId = trainId;
15 _seatCount = seatCount;
16 _fleet = fleet;
17 _reservationInProgress = reservationInProgress;
18 }
19
20 public void Execute()
21 {
22 throw new NotImplementedException();
23 }
24 }
Johnny: Now, let’s add the calls expected in the Statement, but in the opposite order.
Benjamin: To make sure the order is asserted correctly in the Statement?
Johnny: Exactly.
Benjamin: Ok.
1 public void Execute()
2 {
3 var train = _fleet.Pick(_trainId);
4 _fleet.UpdateInformationAbout(train);
5 train.ReserveSeats(seatCount);
6 }
The Statement is still false, this time because of the wrong call order. Now that we have confirmed that we need to make the calls in the right order, I suspect you want me to reverse it, so…
1 public void Execute()
2 {
3 var train = _fleet.Pick(_trainId);
4 train.ReserveSeats(seatCount, reservationInProgress);
5 _fleet.UpdateInformationAbout(train);
6 }
Johnny: Exactly. The Statement is now true. Congratulations!
Benjamin: Now that I look at this code, it’s not protected from any kind of exceptions that might be thrown from either the _fleet or the train.
Johnny: Add that to the TODO list - we will have to take care of that, sooner or later. For now, let’s take a break.
Summary
In this chapter, Johnny and Benjamin used interface discovery again. They used some technical and some domain-related reasons to create a need for new abstractions and design their communication protocols. These abstractions were then pulled into the Statement.
Remember Johnny and Benjamin extended effort when test-driving the controller. This effort paid off now - they were free to shape abstractions mostly outside the constraints imposed by a specific framework.
This chapter does not have a retrospective companion chapter like the previous ones. Most of the interesting stuff that happened here was already explained earlier.
Test-driving value objects
In this chapter, we skip further ahead in time. Johnny and Benjamin just extracted a value object type for a train ID and started the work to further specify it.
Initial value object
Johnny: Oh, you’re back. The refactoring is over – we’ve got a nice value object type extracted from the current code. Here’s the source code of the TrainId class:
1 public class TrainId
2 {
3 private readonly string _value;
4
5 public static TrainId From(string trainIdAsString)
6 {
7 return new TrainId(trainIdAsString);
8 }
9
10 private TrainId(string value)
11 {
12 _value = value;
13 }
14
15 public override bool Equals(object obj)
16 {
17 return _value == ((TrainId) obj)._value;
18 }
19
20 public override string ToString()
21 {
22 return _value;
23 }
24 }
Benjamin: Wait, but we don’t have any Specification for this class yet. Where did all of this implementation come from?
Johnny: That’s because while you were drinking your tea, I extracted this type from an existing implementation that was already a response to false Statements.
Benjamin: I see. So we didn’t mock the TrainId class in other Statements, right?
Johnny: No. This is a general rule – we don’t mock value objects. They don’t represent abstract, polymorphic behaviors. For the same reasons we don’t create interfaces and mocks for string or int, we don’t do it for TrainId.
Value semantics
Benjamin: So, given the existing implementation, is there anything left for us to write?
Johnny: Yes. I decided that this TrainId should be a value object and my design principles for value objects demand it to provide some more guarantees than the ones resulting from a mere refactoring. Also, don’t forget that the comparison of train ids needs to be case-insensitive. This is something we’ve not specified anywhere.
Benjamin: You mentioned “more guarantees”. Do you mean equality?
Johnny: Yes, C# as a language expects equality to follow certain rules. I want this class to implement them. Also, I want it to implement GetHashCode properly and make sure instances of this class are immutable. Last but not least, I’d like the factory method From to throw an exception when null or empty string are passed. Neither of these inputs should lead to creating a valid ID.
Benjamin: Sounds like a lot of Statements to write.
Johnny: Not really. Add the validation and lowercase comparison to TODO list. In the meantime – look – I downloaded a library that will help me with the immutability and equality part. This way, all I need to write is:
1 [Fact] public void
2 ShouldHaveValueObjectSemantics()
3 {
4 var trainIdString = Any.String();
5 var otherTrainIdString = Any.OtherThan(trainIdString);
6 Assert.HasValueSemantics<TrainId>(
7 new Func<TrainId>[]
8 {
9 () => TrainId.From(trainIdString)
10 },
11 new Func<TrainId>[]
12 {
13 () => TrainId.From(otherTrainIdString);
14 },
15 );
16 }
This assertion accepts two arrays:
- the first array contains factory functions that create objects that should be equal to each other. For now, we only have a single example, because I didn’t touch the lowercase vs uppercase issue. But when I do, the array will contain more entries to stress that ids created from the same string with different letter casing should be considered equal.
- the second array contains factory functions that create example objects which should be considered not equal to any of the objects generated by the “equal” factory functions. There is also a single example here as
TrainId’sFrommethod has a single argument, so the only way one instance can differ from another is by being created with a different value of this argument.
After evaluating this Statement, I get the following output:
1 - TrainId must be sealed, or derivatives will be able to override GetHashCode() w\
2 ith mutable code.
3
4 - a.GetHashCode() and b.GetHashCode() should return same values for equal objects.
5
6 - a.Equals(null) should return false, but instead threw System.NullReferenceExcep\
7 tion: Object reference not set to an instance of an object.
8
9 - '==' and '!=' operators are not implemented
Benjamin: Very clever. So these are the rules that our TrainId doesn’t follow yet. Are you going to implement them one by one?
Johnny: Hehe, no. The implementation would be so dull that I’d either use my IDE to generate the necessary implementation or, again, use a library. Lately, I prefer the latter. So let me just download a library called Value and use it on our TrainId.
First off, the TrainId needs to inherit from ValueType generic class like this:
1 public class TrainId : ValueType<TrainId>
This inheritance requires us to implement the following “special” method:
1 protected override IEnumerable<object> GetAllAttributesToBeUsedForEquality
2 {
3 throw new NotImplementedException();
4 }
Benjamin: Weird, what does that do?
Johnny: It’s how the library automates equality implementation. We just need to return an array of values we want to be compared between two instances. As our equality is based solely on the _value field, we need to return just that:
1 protected override IEnumerable<object> GetAllAttributesToBeUsedForEquality
2 {
3 yield return _value;
4 }
We also need to remove the existing Equals() method.
Benjamin: Great, now the only reason for the assertion to fail is:
1 - TrainId must be sealed, or derivatives will be able to override GetHashCode() w\
2 ith mutable code.
I am impressed that this Value library took care of the equality methods, equality operators, and GetHashCode().
Johnny: Nice, huh? Ok, let’s end this part and add the sealed keyword. The complete source code of the class looks like this:
1 public sealed class TrainId : ValueType<TrainId>
2 {
3 private readonly string _value;
4
5 public static TrainId From(string trainIdAsString)
6 {
7 return new TrainId(trainIdAsString);
8 }
9
10 private TrainId(string value)
11 {
12 _value = value;
13 }
14
15 protected override IEnumerable<object> GetAllAttributesToBeUsedForEquality
16 {
17 yield return _value;
18 }
19
20 public override string ToString()
21 {
22 return _value;
23 }
24 }
Benjamin: And the Statement is true.
Case-insensitive comparison
Johnny: What’s left on our TODO list?
Benjamin: Two items:
- Comparison of train ids should be case-insensitive.
-
nulland empty string should not be allowed as valid train ids.
Johnny: Let’s do the case-insensitive comparison, it should be relatively straightforward.
Benjamin: Ok, you mentioned that we would need to expand the Statement you wrote, by adding another “equal value factory” to it. Let me try. What do you think about this?
1 [Fact] public void
2 ShouldHaveValueSemantics()
3 {
4 var trainIdString = Any.String();
5 var otherTrainIdString = Any.OtherThan(trainIdString);
6 Assert.HasValueSemantics<TrainId>(
7 new Func<TrainId>[]
8 {
9 () => TrainId.From(trainIdString.ToUpper())
10 () => TrainId.From(trainIdString.ToLower())
11 },
12 new Func<TrainId>[]
13 {
14 () => TrainId.From(otherTrainIdString);
15 },
16 );
17 }
How about that? From what you explained to me, I understand that by adding a second factory to the first array, I say that both instances should be treated as equal - the one with lowercase string and the one with uppercase string.
Johnny: Exactly. Now, let’s make the Statement true. Fortunately, we can do this by changing the GetAllAttributesToBeUsedForEquality method from:
1 protected override IEnumerable<object> GetAllAttributesToBeUsedForEquality
2 {
3 yield return _value;
4 }
to:
1 protected override IEnumerable<object> GetAllAttributesToBeUsedForEquality
2 {
3 yield return _value.ToLower();
4 }
Aaand done! The assertion checked Equals, equality operators and GetHashCode() and everything seems to be working. We can move on to the next item on our TODO list.
Input validation
Johnny: Let’s take care of the From method - it should disallow null input – we expect an exception when we pass a null inside.
For now, the method looks like this:
1 public static TrainId From(string trainIdAsString)
2 {
3 return new TrainId(trainIdAsString);
4 }
Benjamin: OK, let me write a Statement about the expected behavior:
1 [Fact] public void
2 ShouldThrowWhenCreatedWithANullInput()
3 {
4 Assert.Throws<ArgumentNullException>(() => TrainId.From(null));
5 }
That was easy, huh?
Johnny: Thanks. The Statement is currently false because it expects an exception but nothing is thrown. Let’s make it true by implementing the null check.
1 public static TrainId From(string trainIdAsString)
2 {
3 if(trainIdAsString == null)
4 {
5 throw new ArgumentNullException(nameof(trainIdAsString));
6 }
7 return new TrainId(trainIdAsString);
8 }
Benjamin: Great, it worked!
Summary
Johnny and Benjamin have one more behavior left to specify – throwing an exception when empty string is passed to the factory method – but following them further won’t probably bring us any new insights. Thus, I’d like to close this chapter. Before I do, several points of summary of what to remember about when test-driving value objects:
- Value objects are often (though not always) refactored retroactively from existing code. In such a case, they will already have some coverage from specifications of classes that use these value objects. You don’t need to specify the covered behaviors again in the value object specification. Though I almost always do it, Johnny and Benjamin chose not to and I respect their decision.
- Even if we choose not to write Statements for behaviors already covered by existing specification, we still need to write additional Statements to ensure a type is a proper value object. These Statements are not driven by existing logic, but by our design principles.
- There are many conditions that apply to equality and hash codes of value objects. Instead of writing tests for these behaviors for every value object, I advise to use a generic custom assertion for this. Either find a library that already contains such an assertion, or write your own.
- When implementing equality and hash code methods, I, too, advise to strongly consider using some kind of helper library or at least generating them using an IDE feature.
- We don’t ever mock value objects in Specifications of other classes. In these Specifications, we use the value objects in the same way as
ints andstrings.
Reaching the web of objects boundaries
When doing the outside-in interface discovery and implementing collaborator after collaborator, there’s often a point when we reach the boundaries of our application, which means we must execute some kind of I/O operation (like calling external API via HTTP) or use a class that is part of some kind of third-party package (e.g. provided by our framework of choice). In these places, the freedom with which we could shape our object-oriented reality is hugely constrained by these dependencies. Even though we still drive the behaviors using our intention, we need to be increasingly aware that the direction we take has to match the technology with which we communicate with the outside world at the end.
There are multiple examples of resources that lay on the boundaries of our web of objects. Files, threads, clock, database, communication channels (e.g. http, service bus, websockets), graphical user interfaces… these resources are used to implement mechanisms for things that play roles in our design. We need to somehow reach a consensus between what these implementation mechanisms require to do their job and what is the domain role they need to fit.
In this chapter, I describe several examples of reaching this consensus with some guidance of how to approach them. The gist of this guidance is to hide such dependencies below interfaces that model the roles we need.
What time is it?
Consider getting the current time.
Imagine our application manages sessions that expire at one point. We model the concept of a session by creating a class called Session and allow querying it whether it’s expired or not. To answer that question, the session needs to know its expiry time and the current time, and calculate their difference. In such a case, we can model the source of current time as some kind of “clock” abstraction. Here are several examples of how to do that.
A query for the current time with a mock framework
We can model the clock as merely a service that delivers current time (e.g. via some kind of .CurrentTime() method) The Session class is thus responsible for doing the calculation. An example Statement describing this behavior could look like this:
1 [Fact] public void
2 ShouldSayItIsExpiredWhenItsPastItsExpiryDate()
3 {
4 //GIVEN
5 var expiryDate = Any.DateTime();
6 var clock = Substitute.For<Clock>();
7 var session = new Session(expiryDate, clock);
8
9 clock.CurrentTime().Returns(expiryDate + TimeSpan.FromTicks(1));
10
11 //WHEN
12 var isExpired = session.IsExpired();
13
14 //THEN
15 Assert.True(isExpired);
16 }
I stubbed my clock to return a specific point in time to make executing the Statement deterministic and to describe a specific behavior, which is indication of session expiry.
More responsibility allocation in the clock abstraction
Our previous attempt at the clock abstraction was just to abstract away the services granted by a real time source (“what’s the current time?”). We can make this abstraction fit our specific use case better by giving it a IsPast() method that will accept an expiry time and just tell us whether it’s past that time or not. One of the Statements using the IsPast() method could look like this:
1 [Fact] public void
2 ShouldSayItIsExpiredWhenItsPastItsExpiryDate()
3 {
4 //GIVEN
5 var expiryDate = Any.DateTime();
6 var clock = Substitute.For<Clock>();
7 var session = new Session(expiryDate, clock);
8
9 clock.IsPast(expiryDate).Returns(true);
10
11 //WHEN
12 var isExpired = session.IsExpired();
13
14 //THEN
15 Assert.True(isExpired);
16 }
This time, I am also using a mock to stand in for a Clock implementation.
The upside of this is that the abstraction is more tuned to our specific need. The downside is that the calculation of the time difference between current and expiry time is done in a class that will be at least very difficult to describe with a deterministic Statement as it will rely on the system clock.
A fake clock
If the services provided by an interface such as Clock are simple enough, we can create our own implementation for use in Statements. Such implementation would take away all the pain of working with a real external resource and instead give us a simplified or more controllable version. For example, a fake implementation of the Clock interface might look like this:
1 class SettableClock : Clock
2 {
3 public DateTime _currentTime = DateTime.MinValue;
4
5 public void SetCurrentTime(DateTime currentTime)
6 {
7 _currentTime = currentTime;
8 }
9
10 public DateTime CurrentTime()
11 {
12 return _currentTime;
13 }
14 }
This implementation of Clock has not only a getter for the current time (which is inherited from the Clock interface), but it also has a setter. The “current time” as returned by the SettableClock instance does not come from system clock, but can instead be set to a specific, deterministic value. Here’s how the use of SettableClock looks like in a Statement:
1 [Fact] public void
2 ShouldSayItIsExpiredWhenItsPastItsExpiryDate()
3 {
4 //GIVEN
5 var expiryDate = Any.DateTime();
6 var clock = new SettableClock();
7 var session = new Session(expiryDate, clock);
8
9 clock.SetCurrentTime(expiryDate + TimeSpan.FromSeconds(1));
10
11 //WHEN
12 var isExpired = session.IsExpired();
13
14 //THEN
15 Assert.True(isExpired);
16 }
I could also do the second version of the clock - the one with IsPast() method - in a very similar way. I would just need to put some extra intelligence into the SettableClock, duplicating tiny bits of real Clock implementation. In this case, it’s not a big issue, but there can be cases when this can be an overkill. For a fake to be warranted, the fake implementation must be much, much simpler than the real implementation that we intend to use.
An advantage of a fake is that it can be reused (e.g. a reusable settable clock abstraction can be found in Noda Time and Joda Time libraries). A disadvantage is that the more sophisticated code sits inside the fake, the more probable it is that it will contain bugs. If a very intelligent fake is still worth it despite the complexity (and I’ve seen several cases when it was), I’d consider writing some Specification around the fake implementation.
Other usages of this approach
The same approach as we used with a system clock can be taken with other sources of non-deterministic values, e.g. random value generators.
Timers
Timers are objects that allow deferred or periodic code execution, usually in an asynchronous manner.
Typically, a timer usage is composed of three stages:
- Scheduling the timer expiry for a specific point in time, passing it a callback to execute on expiry. Usually a timer can be told whether to continue the next cycle after the current one is finished, or stop.
- When the timer expiry is scheduled, it runs asynchronously in some kind of background thread.
- When the timer expires, the callback that was passed during scheduling is executed and the timer either begins another cycle, or stops.
From the point of view of our collaboration design, the important parts are point 1 and 3. Let’s tackle them one by one.
Scheduling a periodic task
First let’s imagine we create a new session, add it to some kind of cache and set a timer expiring every 10 seconds to check whether privileges of the session owner are still valid. A Statement for that might look like this:
1 [Fact] public void
2 ShouldAddCreatedSessionToCacheAndScheduleItsPeriodicPrivilegesRefreshWhenExecuted\
3 ()
4 {
5 //GIVEN
6 var sessionData = Any.Instance<SessionData>();
7 var id = Any.Instance<SessionId>();
8 var session = Any.Instance<Session>();
9 var sessionFactory = Substitute.For<SessionFactory>();
10 var cache = Substitute.For<SessionCache>();
11 var periodicTasks = Substitute.For<PeriodicTasks>();
12 var command = new CreateSessionCommand(id, sessionFactory, cache, sessionData);
13
14 sessionFactory.CreateNewSessionFrom(sessionData).Returns(session);
15
16 //WHEN
17 command.Execute();
18
19 //THEN
20 Received.InOrder(() =>
21 {
22 cache.Add(id, session);
23 periodicTasks.RunEvery(TimeSpan.FromSeconds(10), session.RefreshPrivileges);
24 });
25 }
Note that I created a PeriodicTasks interface to model an abstraction for running… well… periodic tasks. This seems like a generic abstraction and might be made a bit more domain-oriented if needed. For our toy example, it should do. The PeriodicTask interface looks like this:
1 interface PeriodicTasks
2 {
3 void RunEvery(TimeSpan period, Action actionRanOnExpiry);
4 }
In the Statement above, I only specified that a periodic operation should be scheduled. Specifying how the PeriodicTasks implementation carries out its job is out of scope.
The PeriodicTasks interface is designed so that I can pass a method group instead of a lambda, because requiring lambdas would make it harder to compare arguments between expected and actual invocations in the Statement. So if I wanted to schedule a periodic invocation of a method that has an argument (say, a single int), I would add a RunEvery method that would look like this:
1 interface PeriodicTasks
2 {
3 void RunEvery(TimeSpan period, Action<int> actionRanOnExpiry, int argument);
4 }
or I could make the method generic:
1 interface PeriodicTasks
2 {
3 void RunEvery<TArg>(TimeSpan period, Action<TArg> actionRanOnExpiry, TArg argume\
4 nt);
5 }
If I needed different sets of arguments, I could just add more methods to the PeriodicTasks interface, provided I don’t couple it to any specific domain-related class (if I needed that, I’d rather split PeriodicTasks into several more domain-specific interfaces).
Expiry
Specifying a case when the timer expires and the scheduled task needs to be executed is even easier. We just need to do what the timer code would do - invoke the scheduled code from our Statement. For my session example, assuming that an implementation of the Session interface is a class called UserSession, I could the following Statement to describe a behavior where losing privileges to access session content leads to event being generated:
1 [Fact] public void
2 ShouldNotifyObserverThatSessionIsClosedOnPrivilegesRefreshWhenUserLosesAccessToSe\
3 ssionContent()
4 {
5 //GIVEN
6 var user = Substitute.For<User>();
7 var sessionContent = Any.Instance<SessionContent>();
8 var sessionEventObserver = Substitute.For<SessionEventObserver>();
9 var id = Any.Instance<SessionId>();
10 var session = new UserSession(id, sessionContent, user, sessionEventObserver);
11
12 user.HasAccessTo(sessionContent).Returns(false);
13
14 //WHEN
15 session.RefreshPrivileges();
16
17 //THEN
18 sessionEventObserver.Received(1).OnSessionClosed(id);
19 }
For the purpose of this example, I am skipping other Statements (for example you might want to specify a behavior when the RefreshPrivileges is called multiple times as that’s what the timer is ultimately going to do) and multithreaded access (timer callbacks are typically executed from other threads, so if they access mutable state, this state must be protected from concurrent modification).
Threads
I usually see threads used in two situations:
- To run several tasks in parallel. For example, we have several independent, heavy calculations and we want to do them at the same time (not one after another) to make the execution of the code finish earlier.
- To defer execution of some logic to an asynchronous background job. This job can still be running even after the method that started it finishes execution.
Parallel execution
In the first case – one of parallel execution – multithreading is an implementation detail of the method being called by the Statement. The Statement itself doesn’t have to know anything about it. Sometimes, though, it needs to know that certain operations might not execute in the same order every time.
Consider the an example, where we evaluate payment for multiple employees. Each evaluation is a costly operation, so implementation-wise, we want to do them in parallel. A Statement describing such operation could look like this:
1 public void [Fact]
2 ShouldEvaluatePaymentForAllEmployees()
3 {
4 //GIVEN
5 var employee1 = Substitute.For<Employee>();
6 var employee2 = Substitute.For<Employee>();
7 var employee3 = Substitute.For<Employee>();
8 var employees = new Employees(employee1, employee2, employee3);
9
10 //WHEN
11 employees.EvaluatePayment();
12
13 //THEN
14 employee1.Received(1).EvaluatePayment();
15 employee2.Received(1).EvaluatePayment();
16 employee3.Received(1).EvaluatePayment();
17 }
Note that the Statement doesn’t mention multithreading at all, because that’s the implementation detail of the EvaluatePayment method on Employees. However, note also that the Statement doesn’t specify the order in which the payment is evaluated for each employee. Part of that is because the order doesn’t matter and the Statement accurately describes that. If, however, the Statement specified the order, it would not only be overspecified, but also could be evaluated as true or false non-deterministically. That’s because in the case of parallel execution, the order in which the methods would be called on each employee could be different.
Background task
Using threads to run background tasks is like using timers that run only once. Just use a similar approach to timers.
Others
There are other dependencies like I/O devices, random value generators or third-party SDKs (e.g. an SDK for connecting to a message bus), but I won’t go into them as the strategy for them is the same - don’t use them directly in your domain-related code. Instead, think about what role they play domain-wise and model that role as an interface. Then use the problematic resource inside a class implementing this interface. Such class will be covered by another kind of Specification which I will cover further in the book.
What’s inside the object?
What are object’s peers?
So far I talked a lot about objects being composed in a web and communicating by sending messages to each other. They work together as peers.
The name peer comes from the objects working on equal footing – there is no hierarchical relationship between peers. When two peers collaborate, none of them can be called an “owner” of the other. They are connected by one object receiving a reference to another object - its “address”.
Here’s an example
1 class Sender
2 {
3 public Sender(Recipient recipient)
4 {
5 _recipient = recipient;
6 //...
7 }
8 }
In this example, the Recipient is a peer of Sender (provided Recipient is not a value object or a data structure). The sender doesn’t know:
- the concrete type of
recipient - when
recipientwas created - by whom
recipientwas created - how long will
recipientobject live after theSenderinstance is destroyed - which other objects collaborate with
recipient
What are object’s internals?
Not every object is part of the web. I already mentioned value objects and data structures. They might be part of the public API of a class but are not its peers.
Another useful category are internals – objects created and owned by other objects, tied to the lifespans of their owners.
Internals are encapsulated inside its owner objects, hidden from the outside world. Exposing internals should be considered a rare exception rather than a rule. They are a part of why their owner objects behave the way they do, but we cannot pass our own implementation to customize that behavior. This is also why we can’t mock the internals - there is no extension point we can use. Luckily we also don’t want to. Meddling into the internals of an object would be breaking encapsulation and coupling to the things that are volatile in our design.
Internals can be sometimes passed to other objects without breaking encapsulation. Let’s take a look at this piece of code:
1 public class MessageChain
2 {
3 private IReadOnlyList<Message> _messages = new List<Message>();
4 private string _recipientAddress;
5
6 public MessageChain(string recipientAddress)
7 {
8 _recipientAddress = recipientAddress;
9 }
10
11 public void Add(string title, string body)
12 {
13 _messages.Add(new Message(_recipientAddress, title, body));
14 }
15
16 public void Send(MessageDestination destination)
17 {
18 destination.SendMany(_messages);
19 }
20 }
In this example, the _messages object is passed to the destination, but the destination doesn’t know where it got this list from, so this interaction doesn’t necessarily expose structural details of the MessageChain class.
The distinction between peers and internals was introduced by Steve Freeman and Nat Pryce in their book Growing Object-Oriented Software Guided By Tests.
Examples of internals
How to discover that an object should be an internal rather than a peer? Below, I listed several examples of categories of objects that can become internals of other objects. I hope these examples help you train the sense of identifying the internals in your design.
Primitives
Consider the following CountingObserver toy class that counts how many times it was notified and when a threshold is reached, passes that notification further:
1 class ObserverWithThreshold : Observer
2 {
3 private int _count = 0;
4 private Observer _nextObserver;
5 private int _threshold;
6
7 public ObserverWithThreshold(int threshold, Observer nextObserver)
8 {
9 _threshold = threshold;
10 _nextObserver = nextObserver;
11 }
12
13 public void Notify()
14 {
15 _count++;
16 if(_count > _threshold)
17 {
18 _count = 0;
19 _nextObserver.Notify();
20 }
21 }
22 }
The _count field is owned and maintained by a CountingObserver instance. It’s invisible outside an ObserverWithThreshold object.
An example Statement of behavior for this class could look like this:
1 [Fact] public void
2 ShouldNotifyTheNextObserverWhenItIsNotifiedMoreTimesThanTheThreshold()
3 {
4 //GIVEN
5 var nextObserver = Substitute.For<Observer>();
6 var observer = new ObserverWithThreshold(2, nextObserver);
7
8 observer.Notify();
9 observer.Notify();
10
11 //WHEN
12 observer.Notify();
13
14 //THEN
15 nextObserver.Received(1).Notify();
16 }
The current notification count is not exposed outside of the ObserverWithThreshold object. I am only passing the threshold that the notification count needs to exceed. If I really wanted, I could, instead of using an int, introduce a collaborator interface for the counter, e.g. called Counter, give it methods like Increment() and GetValue() and the pass a mock from the Statement, but for me, if all I want is counting how many times something is called, I’d rather make it a part of the class implementation to do the counting. It feels simpler if the counter is not exposed.
Value object fields
The counter from the last example was already a value, but I thought I’d mention about richer value objects.
Consider a class representing a commandline session. It allows executing commands within the scope of a working directory. An implementation of this class could look like this:
1 public class CommandlineSession
2 {
3 private AbsolutePath _workingDirectory;
4
5 public CommandlineSession(PathRoot root)
6 {
7 _workingDirectory = root.AsAbsolutePath();
8 }
9
10 public void Enter(DirectoryName dirName)
11 {
12 _workingDirectory = _workingDirectory.Append(dirName);
13 }
14
15 public void Execute(Command command)
16 {
17 command.ExecuteIn(_workingDirectory);
18 }
19 //...
20 }
This CommandlineSession class has a private field called _workingDirectory, representing the working directory of the current commandline session. Even though the initial value is based on the constructor argument, the field is managed internally from there on and only passed to a command so that it knows where to execute. An example Statement for the behavior of the CommandLineSession could look like this:
1 [Fact] public void
2 ShouldExecuteCommandInEnteredWorkingDirectory()
3 {
4 //GIVEN
5 var pathRoot = Any.Instance<PathRoot>();
6 var commandline = new CommandLineSession(pathRoot);
7 var subDirectoryLevel1 = Any,Insytance<DirectoryName>();
8 var subDirectoryLevel2 = Any,Insytance<DirectoryName>();
9 var subDirectoryLevel3 = Any,Insytance<DirectoryName>();
10 var command = Substitute.For<Command>();
11
12 commandline.Enter(subDirectoryLevel1);
13 commandline.Enter(subDirectoryLevel2);
14 commandline.Enter(subDirectoryLevel3);
15
16 //WHEN
17 commandLine.Execute(command);
18
19 //THEN
20 command.Received(1).Execute(
21 AbsolutePath.Combine(
22 pathRoot,
23 subDirectoryLevel1,
24 subDirectoryLevel2,
25 subDirectoryLevel3));
26 }
Again, I don’t have any access to the internal _workingDirectory field. I can only predict its value and create an expected value in my Statement. Note that I am not even using the same methods to combine the paths in both the Statement and the production code - while the production code is using the Append() method, my Statement is using a static Combine method on an AbsolutePath type. This shows that my Statement is oblivious to how exactly the internal state is managed by the CommandLineSession class.
Collections
Raw collections of items (like lists, hashsets, arrays etc.) aren’t typically viewed as peers. Even if I write a class that accepts a collection interface (e.g. IList in C#) as a parameter, I never mock the collection interface, but rather, use one of the built-in collections.
Here’s an example of a InMemorySessions class initializing and utilizing a collection:
1 public class InMemorySessions : Sessions
2 {
3 private Dictionary<SessionId, Session> _sessions
4 = new Dictionary<SessionId, Session>();
5 private SessionFactory _sessionFactory;
6
7 public InMemorySessions(SessionFactory sessionFactory)
8 {
9 _sessionFactory = sessionFactory;
10 }
11
12 public void StartNew(SessionId id)
13 {
14 var session = _sessionFactory.CreateNewSession(id);
15 session.Start();
16 _sessions[id] = session;
17 }
18
19 public void StopAll()
20 {
21 foreach(var session in _sessions.Values())
22 {
23 _session.Stop();
24 }
25 }
26 //...
27 }
The dictionary used here is not exposed at all to the external world. It’s only used internally. I can’t pass a mock implementation and even if I could, I’d rather leave the behavior as owned by the InMemorySessions. An example Statement for the InMemorySessions class demonstrated how the dictionary is not visible outside the class:
1 [Fact] public void
2 ShouldStopAddedSessionsWhenAskedToStopAll()
3 {
4 //GIVEN
5 var sessionFactory = Substitute.For<SessionFactory>();
6 var sessions = new InMemorySessions(sessionFactory);
7 var sessionId1 = Any.Instance<SessionId>();
8 var sessionId2 = Any.Instance<SessionId>();
9 var sessionId3 = Any.Instance<SessionId>();
10 var session1 = Substitute.For<Session>();
11 var session2 = Substitute.For<Session>();
12 var session3 = Substitute.For<Session>();
13
14 sessionFactory.CreateNewSession(sessionId1)
15 .Returns(session1);
16 sessionFactory.CreateNewSession(sessionId2)
17 .Returns(session2);
18 sessionFactory.CreateNewSession(sessionId3)
19 .Returns(session3);
20
21 sessions.StartNew(sessionId1);
22 sessions.StartNew(sessionId2);
23 sessions.StartNew(sessionId3);
24
25 //WHEN
26 sessions.StopAll();
27
28 //THEN
29 session1.Received(1).Stop();
30 session2.Received(1).Stop();
31 session3.Received(1).Stop();
32 }
Toolbox classes and objects
Toolbox classes and objects are not really abstractions of any specific problem domain, but they help make the implementation more concise, reducing the number of lines of code I have to write to get the job done. One example is a C# Regex class for regular expressions. Here’s an example of a line count calculator that utilizes a Regex instance to count the number of lines in a piece of text:
1 class LocCalculator
2 {
3 private static readonly Regex NewlineRegex
4 = new Regex(@"\r\n|\n", RegexOptions.Compiled);
5
6 public uint CountLinesIn(string content)
7 {
8 return NewlineRegex.Split(contentText).Length;
9 }
10 }
Again, I feel like the knowledge on how to split a string into several lines should belong to the LocCalculator class. I wouldn’t introduce and mock an abstraction (e.g. called a LineSplitter unless there were some kind of domain rules associated with splitting the text). An example Statement describing the behavior of the calculator would look like this:
1 [Fact] public void
2 ShouldCountLinesDelimitedByCrLf()
3 {
4 //GIVEN
5 var text = $"{Any.String()}\r\n{Any.String()}\r\n{Any.String()}";
6 var calculator = new LocCalculator();
7
8 //WHEN
9 var lineCount = calculator.CountLinesIn(text);
10
11 //THEN
12 Assert.Equal(3, lineCount);
13 }
The regular expression object is nowhere to be seen - it remains hidden as an implementation detail of the LocCalculator class.
Some third-party library classes
Below is an example that uses a C# fault injection framework, Simmy. The class decorates a real storage class and allows to configure throwing exceptions instead of talking to the storage object. The example might seem a little convoluted and the class isn’t production-ready anyway. Note that a lot of classes and methods are only used inside the class and not visible from the outside.
1 public class FaultInjectablePersonStorage : PersonStorage
2 {
3 private readonly PersonStorage _storage;
4 private readonly InjectOutcomePolicy _chaosPolicy;
5
6 public FaultInjectablePersonStorage(bool injectionEnabled, PersonStorage storage)
7 {
8 _storage = storage;
9 _chaosPolicy = MonkeyPolicy.InjectException(with =>
10 with.Fault(new Exception("thrown from exception attack!"))
11 .InjectionRate(1)
12 .EnabledWhen((context, token) => injectionEnabled)
13 );
14 }
15
16 public List<Person> GetPeople()
17 {
18 var capturedResult = _chaosPolicy.ExecuteAndCapture(() => _storage.GetPeople());
19 if (capturedResult.Outcome == OutcomeType.Failure)
20 {
21 throw capturedResult.FinalException;
22 }
23 else
24 {
25 return capturedResult.Result;
26 }
27 }
28 }
An example Statement could look like this:
1 [Fact] public void
2 ShouldReturnPeopleFromInnerInstanceWhenTheirRetrievalIsSuccessfulAndInjectionIsDi\
3 sabled()
4 {
5 //GIVEN
6 var innerStorage = Substitute.For<PersonStorage>();
7 var peopleInInnerStorage = Any.List<Person>();
8 var storage = new FaultInjectablePersonStorage(false, innerStorage);
9
10 innerStorage.GetPeople().Returns(peopleFromInnerStorage);
11
12 //WHEN
13 var result = storage.GetPeople();
14
15 //THEN
16 Assert.Equal(peopleInInnerStorage, result);
17 }
and it has no trace of the Simmy library.
Summary
In this chapter, I argued that not all communication between objects should be represented as public protocols. Instead, some of it should be encapsulated inside the object. I also provided several examples to help you find such internal collaborators.
Design smells visible in the Specification
In the earlier chapters, I stressed many times how mock objects can and should be used as a design tool, helping flesh out the protocols between collaborating objects. This is because something I call Test-design approach mismatch exists. The way I chose to phrase it is:
“Test automation approach and design approach need to live in symbiosis - build on a similar sets of assumptions and towards the same goals. If they do, they reinforce each other. If they don’t, there will be friction between them.”
I find this to be universally true. For example, if I test an asynchronous application as if I was testing a synchronous application or if I try to test JSON web API using a clicking tool, my code will either not work correctly or look weird. I will probably need to put extra work to compensate for the mismatch between the testing and design approach. For this reason some who prefer much different design approaches consider mock objects a smell.
I am seeing it on the unit level as well. In this book, I am using the specification terminology instead of testing terminology. Thus I can say that by writing my Specification on unit level and using mock objects in my Statements, I expect my design to have certain properties and I find these properties desirable. If my code does not show these properties, it will make my life harder. The other side of this coin is that if I train myself to recognize the situations where the design and specification approaches diverge and patterns by which it happens, I can use this knowledge to improve my design.
In this chapter, I present some of the smells that can be seen in the Statements but are in reality design issues. All of these smells come from the community and you can find them catalogued in other places.
Design smells’ catalog
Specifying the same behavior in many places
Sometimes, when I write a Statement, I have a deja vu and start thinking “I remember specifying a behavior like this already”. I will feel this especially when that other time is not so long ago, maybe even a couple of minutes.
Consider the following Statement describing a reporting rule generation logic:
1 [Fact] public void
2 ShouldReportAllEmployeesWhenExecuted()
3 {
4 //GIVEN
5 var employee1 = Substitute.For<Employee>();
6 var employee2 = Substitute.For<Employee>();
7 var employee3 = Substitute.For<Employee>();
8 var allEmployees = new List<Employee>() {employee1, employee2, employee3};
9 var reportBuilder = Substitute.For<ReportBuilder>();
10 var employeeReportingRule = new EmployeeReport(allEmployees, reportBuilder);
11
12 //WHEN
13 employeeReportingRule.Execute();
14
15 //THEN
16 Received.InOrder(() =>
17 {
18 reportBuilder.AddHeader();
19 employee1.AddTo(report);
20 employee2.AddTo(report);
21 employee3.AddTo(report);
22 reportBuilder.AddFooter();
23 });
24 }
Now, another Statement for a different rule:
1 [Fact] public void
2 ShouldReportTotalCost()
3 {
4 //GIVEN
5 var calculatedTotalCost = Any.Integer();
6 var reportBuilder = Substitute.For<ReportBuilder>();
7 var costReportingRule = new EmployeeReport(totalCost, reportBuilder);
8
9 //WHEN
10 employeeReportingRule.Execute();
11
12 //THEN
13 Received.InOrder(() =>
14 {
15 reportBuilder.AddHeader();
16 reportBuilder.AddTotalCost(totalCost);
17 reportBuilder.AddFooter();
18 });
19 }
And note that I have to specify again that a header and a footer is added to the report. This might mean there is redundancy in the design and maybe every report or at least most of them, need a header and a footer. This observation, amplified of the feeling of pointlessness from describing the same behavior several times, may lead me to try to extract some logic into a separate object, maybe a decorator, that will add footer before the main part of the report and footer at the end.
When this is not a problem
The trick with recognizing redundancy in the design is to detect whether the behavior in both places will change for the same reasons and in the same way. This is usually true if both places duplicate the same domain rule. But consider these two functions:
1 double CmToM(double value)
2 {
3 return value/100;
4 }
5
6 double PercentToFraction(double value)
7 {
8 return value/100;
9 }
While the implementation is the same, the domain rule is different. In such situations, we’re not talking about redundancy, but coincidence. Also see Vladimir Khorikov’s excellent post on this topic.
Redundancy in the code is often referred to as “DRY (don’t repeat yourself) violation”. Remember that DRY is about domain rules, not code.
Many mocks
So far, I have advertised using mocks in modelling roles in the Statements. So, mocking is good, right? Well, not exactly. Through mock objects, we can see how the class we specify interacts with its context. If the interactions are complex, then the Statements will show that. This isn’t something we should ignore. Quite the contrary, the ability to see this problem through our Statements is one of the main reasons we use mocks.
One of the symptoms of the design issues is that we have too many mocks. We can feel the pain either when writing the Statement (“oh no, I need another mock”) or when reading (“so many mocks, I can’t see where something real happens”).
Doing too much
It may be that a class needs lots of dependencies because it does too much. It may do validation, read configuration, page through persistent records, handle errors etc. The problem with this is that when the class is coupled to too many things, there are many reasons for the class to change.
A remedy for this could be to extract parts of the logic into separate classes so that our specified class does less with less peers.
Mocks returning mocks returning mocks
Mocks returning mocks returning mocks typically mean that our code is not built around the tell don’t ask heuristic and that the specified class is coupled to too many types.
Many unused mocks
If we are passing many mocks in each Statement only to fill the parameter list, it may be a sign of poor cohesion.
Having many unused mocks is OK in facade classes, because the reasons for facades to exist is to simplify access to a subsystem and they typically hold references to many objects, doing little with each of them.
Mock stubbing and verifying the same call
Consider an example where our application manages some kind of resource and each requester obtains a “slot” in this resource. The resource is represented by the following interface:
1 interface ISharedResource
2 {
3 public SlotId AssignSlot();
4 }
when specifying the behaviors of any class that uses this resource, will use a mock:
1 var resource = Substitute.For<ISharedResource>();
we want to describe that a slot reservation is attempted in a shared resource only once, so we will have the following call on our mock:
1 resource.Received(1).AssignSlot();
But we will also need to describe what happens when the slot is successfully assigned or when the assignment fails, so we need to configure the mock to return something:
1 resource.AssignSlot().Returns(slotId);
Having both setting up a return value and verification of the call to same method in a Statement is a sign we are violating the command-query separation principle. This will limit our ability to elevate polymorphism in implementations of this interface.
But what can we do about this?
pass a continuation
We may pass a more full-featured role that will continue the flow. Below is an example of passing a cache where the assigned slot will be saved if the assignment is successful:
1 resource.Received(1).AssignSlot(cache);
pass a callback
we may create a special role for a recipient of the result and pass it inside the AssignSlot method, then make it act on the result:
1 Received.InOrder(() =>
2 {
3 resource.AssignSlot(assignSlotResponse);
4 assignSlotResponse.Received(1).DisplayOn(screen);
5 })
pass a the needed collaborators through the constructor
Sometimes we may completely remove the notion of result from the interface and make passing it further an implementation detail. Here is the Statement where we don’t specify anything about handling a potential result:
1 resource.Received(1).AssignSlot();
the implementations are still free to do what they want (or nothing at all) depending on assignment success or failure. One such implementation might have a constructor that looks like this:
1 public CacheBackedSharedResource(IAssignmentCache cache) { ... }
and may use its constructor argument to delegate some of the logic there.
When is this not a problem?
TODO: I/O boundary - sometimes easier to return something than to pass a collaborator through an architectural boundary.
Trying to mock a third-party interface
The classic book on TDD, Growing Object-Oriented Software Guided By Tests, suggests to not mock types you don’t own. Because mocks are a design tool, we only want to create mocks of types we can control and which represent abstractions. Otherwise we are tied by the definition of the interface “as is” and cannot improve our Statements by improving the design.
Does it mean that if I have a File class, I can just wrap it in a thin interface and create an IFile interface to enable mocking and I’m good? If the constraint I place on this interface is that it’s 1:1 to the type I don’t own, then it’s back to square one - I still have an interface which is defined in a way that may make writing Statements with mocks hard and there is nothing I can do about it.
So what should I do when I am close to the I/O boundary? I should use a different type of Statements, which I will show you in one of the next parts of this book.
What about logging?
The GOOS book also discusses this topic - loggers from libraries are often ubiquitous all over many codebases. TODO https://learn.microsoft.com/en-us/dotnet/core/extensions/logger-message-generator and Support class.
Blinking tests because of generated data
This specific smell is not related to mock objects, but rather to using constrained non-determinism.
When a Statement becomes unstable due to data being generated as different values on each execution, this might mean that the behavior is too reliant of data and lacks abstraction. The code might look like this:
1 if(customer.Accounts.First(a => a.IsPrimary).IsActive)
2 {
3 customer.Status = CustomerStatus.Active;
4 }
Note that code checks the IsActive flag (a boolean) of the first account set as primary (again, using a boolean IsPrimary flag). Booleans are especially vulnerable to non-deterministic generation because they have two possible values, each of which is typically in a different equivalence class. And here we have two. So I would expect Statements that use generated data to fail regularly in a non-deterministic way.
One of the possible solutions here could be to create an abstraction over the customer data structure and use a mock instead of a generated data structure. Something like this:
1 if(customer.HasActiveAccount())
2 {
3 customer.Activate();
4 }
This solution is the naive, textbook solution. In reality, we might even end up rethinking the whole concept of activation and it could lead us to inventing new interesting roles.
Many times, I encountered people using data generators such as Any as a free pass to pass monstrous and complicated data structures around (because “why not? I can generate it with a single call to Any.Instance<>()”). However, I believe they should be working in the opposite direction - by introducing the notion of non-determinism, they force me to be more paranoid about the context in which my object works to keep “moving parts” of it to a minimum.
TODO: example
TODO: lack of abstraction
TODO: do I need this?
Too fine-grained role separation
can we introduce too many roles? Probably.
For example, we might have a cache class playing three roles: ItemWriter for saving items, ItemReader for reading items and ExpiryCheck for checking if a specific item is expired. While I consider striving for more fine-grained roles to be a good idea, if all are used in the same place, we now have to create three mocks for what seems like a consistent set of obligations scattered across three different interfaces.
General description
what does smell mean? why test smells can mean code smells?
Flavors
- testing the same thing in many places
- Redundancy
- When this is not a problem - decoupling of different contexts, no real redundancy
- Many mocks/ Bloated constructor (GOOS) / Too many dependencies (GOOS)
- Coupling
- Lack of abstractions
- Not a problem when: facades
- Mock stubbing and verifying the same call
- Breaking CQS
- When not a problem: close to I/O boundary
- Blinking test (Any + boolean flags and ifs on them, multiple ways of getting the same information)
- too much focus on data instead of abstractions
- lack of data encapsulation
- When not a problem: when it’s only a test problem (badly written test). Still a problem but not design problem.
- Excessive setups (sustainabletdd) - both preparing data & preparing the unit with additional calls
- chatty protocols
- issues with cohesion
- coupling (e.g. to lots of data that is needed)
- When not a problem: higher level tests (e.g. configuration upload) - maybe this still means bad higher level architecture?
- Combinatorial explosion (TODO: confirm name)
- cohesion issues?
- too low abstraction level (e.g. ifs to collection)
- When not a problem: decision tables (domain logic is based on many decisions and we already decoupled them from other parts of logic)
- Need to assert on private fields
- Cohesion
- pass-through tests (test simple forwarding of calls)
- too many layers of abstraction.
- Also happens in decorators when decorated interfaces are too wide
- Set up mock calls consistency (many ways to get the same information, the tested code can use either, so we need to setup multiple calls to behave consistently)
- Might mean too wide interface with too many options
- or insufficient level of abstraction
- overly protective test (sustainabletdd) - checking other behaviors than only the intended one out of fear
- lack of cohesion
- hidden coupling?
- Having to mock/prepare framework/library objects (e.g. HttpRequest or sth.)
- Don’t mock what you don’t own?
- In domain code - coupling to tech details
- Liberal matchers (lost control over how objects flow through methods)
- cohesion
- chatty protocols
- separate usage from construction
- Encapsulating the protocol (hiding mock configuration and verification behind helper methods because they are too complex or repeat in each test)
- Cohesion (the repeated code might be moved to a separate class)
- I need to mock object I can’t replace (GOOS)
- Mocking concrete classes (GOOS)
- Mocking value-like objects (GOOS)
- making objects instead of values
- Confused object (GOOS)
- Too many expectations (GOOS)
- Many tests in one (hard to setup)
- also look here: https://www.yegor256.com/2018/12/11/unit-testing-anti-patterns.html
THIS IS ALL I HAVE FOR NOW. WHAT FOLLOWS IS RAW, UNORDERED MATERIAL THAT’S NOT YET READY TO BE CONSUMED AS PART OF THIS TUTORIAL
Mock objects as a design tool
Responsibility-Driven Design
TDD with mocks follows RDD, so what? Model roles. Everything else might go into values and internals. But there is already a metaphor of the web
Ok, so maybe a CRC card?
Not every class is part of the web. One example is value objects. Another example are simple data structures, or DTOs.
Guidance of test smells
Long Statements
Lots of stubbing
Specifying private members
Revisiting topics from chapter 1
Constrained non-determinism in OO world
Passive vs active roles
Behavioral boundaries
Triangulation
Maintainable mock-based Statements
Setup and teardown
Refactoring mock code
until you pop it out through the constructor, it’s object’s private business.
mocks rely on the boundaries being stable. If wrong on this, tests need to be rewritten, but the feedbak from tests allows stabilizing the boundaries further. And there are not that many tests to change as we test small pieces of code.
well-designed interactions should limit the impact of change