note that when I say "statement" in this blog post, I mean "unit test".
For some time, I am witnessing discussions whether using mocks is something that "breaks encapsulation" and thus should be used sparingly. The time has come for me to voice my opinion.
Is "breaking encapsulation" even the right charge?
First of all, we must decrypt what "breaking encapsulation" means. It's not so simple actually, for two reasons.
The first reason is that encapsulation is understood differently by different people and is sometimes used synonymously with information hiding and sometimes a distinction is made between the two.
If we decide to simplify things and treat encapsulation as "information hiding", it is still unclear what it means to "break" it. Does it mean to "reveal information"? In this case, almost any object breaks encapsulation, because any object reveals
So, what does it mean to "break encapsulation" in case of charges made against mocks?
What is the actual charge?
The thing many people have problem with is that unit-level specification makes statements on how an object uses (or more precisely, communicates with) its collaborators. For example, let's assume that I have a class that sends a packet through a connection and write the following unit-level statement:
[Test] public void ShouldOpenConnectionThenSendAndThenCloseItWhenAskedToSendPacket() { //GIVEN var connection = Substitute.For<Connection>(); var sending = new Sending(connection); var packet = Any.Instance<Packet>(); //WHEN sending.PerformFor(packet); //THEN Received.InOrder(() => { connection.Open(); connection.Send(packet); connection.Close(); }); }
Note that this statement is coupled not only to the public methods of a class, but also to the way it uses objects of another class. The usual charge made against such statements is based on the following deduction chain:
- How an object uses its collaborators is not part of the object's public interface, hence it's an implementation detail
- Mocks are all about how an object uses its collaborators
- So it seems that statements using mocks are coupled to the implementation details of specified class
- If we change the class so that it uses different collaborators or uses them differently, it may still do its job from the perspective of the clients of those objects (who just call the methods and expect return values). But, the statements will be failing in such case, because they're coupled to how object uses its collaborators.
- The statements using mocks are brittle
- Mocks should be avoided
- Well... unless they're necessary to get high code coverage in places where just invoking methods and checking results is not enough
In the next section, I'll try to point why I think this kind of thinking is flawed.
Dependency Injection changes the game
The first point of this deduction chain we just talked about tells that it's object's private business who and how it calls to achieve this goal.
This may even be true for an object that is instantiated like this:
var sending = new Sending();
But is utterly false when the object is instantiates like this:
Connection c = ...; //get object that implements Connection interface var sending = new Sending(connection);
What is changed, you may ask - it's just adding a constructor parameter! Well, no it's not just this. The point is, the Sending
class now can have its dependency injected. This is very important, because the whole point of dependency injection is that the object is not the owner of its dependencies. Thus, such object effectively says: "Listen up, my client, it's your problem what you give me, as long as I am able to use it". In other words, each client of the Sending
class can have their own class implementing the Connection
interface and each new user of the class can write their own implementation if they wish so.
So now the real question kicks in:
Remember, you can't just implement all methods of an Connection
interface anyway you want. By doing so, you have a nice chance to violate the Liskov Substitution Principle. Thus, the custom implementation of Connection
a client provides must not only implement the interface, it must also adhere to a certain protocol required by the Sending
class that will be using it.
This leads us to one conclusion: if a custom implementation of Connection
must be coded with a knowledge of the protocol between Sending
and Connection
, this protocol is not a private business of Sending
class. While it may not be part of the public API, it's still part of the public contract.
Other examples
There are many examples out there that what I just wrote is true. For example, let's look at JEE Servlet lifecycle documentation and see what we can read there:
The lifecycle of a servlet is controlled by the container in which the servlet has been deployed. When a request is mapped to a servlet, the container performs the following steps.
- If an instance of the servlet does not exist, the web container
- Loads the servlet class.
- Creates an instance of the servlet class.
- Initializes the servlet instance by calling the init method. Initialization is covered in Creating and Initializing a Servlet.
- Invokes the service method, passing request and response objects. Service methods are discussed in Writing Service Methods.
- If it needs to remove the servlet, the container finalizes the servlet by calling the servlet’s destroy method. For more information, see Finalizing a Servlet.
For comparison, let's take a look at what we can read at ASP.NET web page life cycle documentation. Again, there's a table where we can find the names of the methods called at each stage, the order of the methods called and what we can expect when implementing each method.
These aren't necessarily examples of dependency injection, but are examples of inversion of control, which DI is a particular application of.
So, ekhem... would you really say that putting knowledge on what methods and in what order are performed breaks encapsulation of JSP container or ASP.NET runtime? Or, in better words, is it exposing implementation details? No, it's just another part of the class public contract. Another example might be unit-testing frameworks, where you write so called "test classes" knowing, that "Setup" method is called before a test and "teardown" is called after.
And this is my view on the whole encapsulation dillema - as soon as you make the dependencies of the class injectable (or use any other forms of inversion of control), how this class uses those dependencies becomes a part of public contract of a class, not private implementation detail. This public contract should be specified and thus we write unit level statements unit mock objects.
Of course, nothing, never, prevents you from exposing private implementation details while using any technique, it's just that mocks don't seem different in this regard in any particular way.
What do YOU think?