Project Management

Mock Objects, Part 2

From the Sustainable Test-Driven Development Blog
by
Test-driven development is a very powerful technique for analyzing, designing, and testing quality software. However, if done incorrectly, TDD can incur massive maintenance costs as the test suite grows large. This is such a common problem that it has led some to conclude that TDD is not sustainable over the long haul. This does not have to be true. It's all about what you think TDD is, and how you do it. This blog is all about the issues that arise when TDD is done poorly—and how to avoid them.

About this Blog

RSS

Recent Posts

ATDD and TDD

The Importance of Test Failure

Mock Objects, Part 1

Mock Objects, Part 2

Mock Objects, Part 3



Techniques

There are many ways to create a mock object by hand.  You will likely come up with your own techniques, which may make use of language elements and idioms made possible by the particular languages and frameworks you work with.  It is important to know more than one technique because under various circumstances during the development process we are able and unable to change different things.  Also, we may be dependent on the work of other teams or organizations who might not have created an ideal situation for us if we seek to write the kinds of tests this book is about.

For example, let’s say the group who implemented the GPS system did not create a separate interface for their implementation, but instead just created a concrete API object that gives us access to the hardware:
 

No Interface for the GPS dependency

Perhaps the DashLight implementation is part of our responsibility, and so we’ve opted to create a separate interface which makes mocking easy.  The GPS team might not have (note the GPS object above is a concrete implementation which we are directly dependent upon).  To test our object (BoundaryAlarm) we must bring the GPS under the control of the test.

I suppose we could take our testing laptop out into the field, hook it up to the real global positioning hardware, and physically move the system around the field, running the test in different locations.  At some point, no doubt, such testing will take place.  But remember, in TDD we are not really testing, we are specifying, and the spec should be useful and runnable at any point in time, under any circumstances.  So, we must mock the GPS class.

Let’s start simply:

Direct Inheritance

Here we have simply sub-classed the “real” GPS class.   As was the case when GPS was an interface, our mock will up-cast and appear (to BoundaryAlarm) to be the real thing.  The extra method SetCurrentLocation() will be available to the test, for conditioning purposes, because the test will hold the reference to the mock in a down-cast to its actual type.

But this technique may not work, or may product negative effects.

  • If GPS is not able to be sub-classed (it is final, or sealed, or whatever your language would call a class that cannot be sub-classed), then this is obviously not possible.
  • If the language you are using has both virtual and non-virtual methods, the GetCurrentLocation() method must be a virtual method, otherwise the up-cast of the mock will cause the original method to actually be called, rather than the mock’s method, and the test will not work.
  • In sub-classing, you create strong coupling between the mock and the original class.  One effect of this is the fact that when the test creates an instance of the mock (using new MockGPS()), the class loader will also create an instance of the base class (GPS) in the background.  It must do this, as the original implementation methods are available to the sub-class (via Base() or super() or a similar method, or by direct access).  If merely creating an instance of GPS is a disadvantage (it slows down the test, or requires that the actual hardware must be present when the test runs, etc…) then sub-classing like this is something we don’t want.

Obviously if we could change the GPS class, we’d opt to create that separate interface and eliminate all of these problems.  But what if we cannot?

We could wrap the interface in an adapter (we’ll leave DashLight out of the discussion from this point forward):

Wrapping the dependency

GPSWrapper is our class, which we created just for this purpose, so we could mock it.  Clearly we would have to change BoundaryAlarm in this case, as it no longer directly depends on GPS but rather on the GPSWrapper, and these two are not interchangeable.  But, if we can change BoundaryAlarm and cannot change GPS, then this technique is appropriate.

Note that the adapter and the “real” GPS have the same method name in this example.  This is for simplicity; we could have called the method in the adapter anything we chose.  If the team implementing the real GPS chose a method name we disliked (maybe we think it is unclear, or overly generic) this is also an opportunity to change this, and to make our code more readable.

Note also that the adapter is kept extremely simple; we are not going to be able to test it in TDD (we will do so in integration testing, but those are not run frequently) and so we really don’t want it to have any kind of complex behavior that might fail.  We’ll probably do something like this:

public GPSWrapper {
    private GPS myGPS;
    public GPSWrapper() {
        myGPS = new GPS();
    }
    public virtual Location GetCurrentLocation() {
        return myGPS.GetCurrentLocation();
    }
}


There’s very little to this, and that’s intentional.  Whenever we create object like this (to wrap any dependency, like the database, the UI, the network, whatever) we call them “periphery objects” because they live on the boundary between the system we are developing and are responsible for, and other systems that we seek to control by mocking.  We obviously cannot write TDD-style tests for these objects, and so we always keep them as minimal as possible.

This solves most of the problems of direct inheritance.  Even if GPS is sealed and has non-virtual methods, our wrapper does not have these problems since we can create it any way we like.  However, note that the inheritance from the mock to the wrapper still means that the real wrapper will be instantiated at test time, and since the wrapper creates an instance of the original GPS object we may still find our test is too slow, or can only be run in the presence of the hardware, etc…  If this is a problem, we can take this one step further, and create an interface for wrapping:

Interface for Wrapping

This eliminates the inheritance coupling between the mock and the implementation entirely, and thus we create no instance of the actual wrapper implementation (GPSWrapperImp) at test time.  Our tests will run fast, be completely repeatable, and will not require the actual GPS system to run.

These are all techniques for creating mocks by hand.  Another approach is to use a mocking framework, a tool that creates these mocks for you.  We’ll examine an example of such a tool a bit later on, and also discuss what we like and dislike about the use of such tools.  In any case, whether you automate you mocks or write them by hand, every developer should know how to handcraft them, how they work, and what they do.


Next, in part 3, we’ll deal with different ways of injecting the mock into the class under test.  In these examples the issue was simple: the constructor of BoundaryAlarm takes its two dependencies as parameters, allowing the test to send in the mock instead of the actual object.  But what if we didn’t have this?  We need more techniques, and we’ll examine a few.

 


 

Part 1: https://www.projectmanagement.com/blog-post/68267/Mock-Objects--Part-1

Part 2: https://www.projectmanagement.com/blog-post/68254/Mock-Objects--Part-2

Part 3: https://www.projectmanagement.com/blog-post/68253/Mock-Objects--Part-3

Posted on: February 12, 2021 01:51 AM | Permalink

Comments (0)

Please login or join to subscribe to this item


Please Login/Register to leave a comment.

ADVERTISEMENTS

"Man prefers to believe what he prefers to be true."

- Francis Bacon

ADVERTISEMENT

Sponsors