Unit Testing with Service Stubs or Mock Types

Introduction

The simple fact of life is that ideas overlap and sometimes are attributed to multiple sources. For instance, I grew learning that Marconi invented radio. Turns out that Tesla invented the radio and now holds the patent posthumously.

An overlapping idea that I believe—but don't quote me—came out of Agile software development is the concept of mock types. In the patterns world "mock types" are called "service stubs." Who invented the idea is important to that person, but the concept of whether we call it mock type or service stub is important to us.

The basic idea is that many of us are building distributed applications with essential third-party services, and sometimes those third-party services are unavailable. Mock types (or service stubs; I will use mock types for the rest of this article) represent a strategy for handling critical services, fostering forward momentum even when those services may be temporarily disrupted.

For example, I worked on a project recently that interacted with AAMVA—the American Association of Motor Vehicle Administrators. AAMVA is an external resource that was critical to our application. In our environment, AAMVA only provided an e connection. This meant that if multiple developers were working on AAMVA, they either had to coordinate their time on AAMVA, or if AAMVA was unavailable no one could test their AAMVA code.

The basic idea is that if you have critical services in your applications topology, you can and should build mock types for when those services are disrupted for whatever reason. For your purposes, a service can be anything that is external to any consuming code. Web services, third-party providers, the difference between your client layer and business layer. Roughly anything that might be unavailable during development and unit testing can use the mock type strategy.

Conceptualizing Mock Types

For your purposes, simplify the concept of a service to a provider—some code that provides some capability—that is non-trivial or if trivial, supplied by a distributed, remote, or third party. Many times, the first step taken is to code write to the interface of the provider and connect that to some part of your application. Mock types add a level of abstraction, an interface. The interface contract is the capabilities of the provider—functions and properties.

The next step is to implement a class that is the concrete provider at some point. The mock type is represented by a concrete provider that simply satisfies the contract of the interface. The easiest way to implement a mock type is to define a class, realize the interface, and return hard-coded values. Remember that a mock type is a stub that exists to simulate, or mock up, the capabilities of the provider so that you can code or test the consumer. (The consumer being your code that will ultimately use the provider.

Tip: The key concept for mock type strategies is that interdependent parts of your application come together at different times. If consumer A is dependent on provider B and B isn't ready, a stub for provider B permits A's development to proceed.

In your consumer, declare an instance of the interface and then initialize an instance of the actual provider you want. If you use a factory class to return the provider instance, you can add additional information—for example, to your App.config file—that determines when to return the mock type and when to return the concrete type. Figure 1 shows a simple class diagram that illustrates the mock type relationships.

Figure 1: Use a stubbed out class that returns simple data or hard-coded data as a place holder for an actual piece of code that may be unavailable at times.

Implementing Mock Types

Some of you may not read UML. Some of you, so UML diagrams and written explanations are usually balanced nicely with code. In the example for this article, there is a service called NorthwindService implemented in WCF. The service returns a customer based on a CustomerID. Underneath the service is LINQ to SQL code that does the database work and the WCF service contract uses a LINQ query to extract just the customer desired and returns that Customer object.

Tip: For the demo I used SQL Server 2005, the Northwind database, and LINQ to SQL all with Visual Studio 2008, .NET 3.5, and VB9.

The WCF service implementation is shown in Listing 1. Listing 2 provides the ServiceContract, the DataContext, and the ORM (object relational mapping) mapped table. The ServiceContract is part of WCF. The DataContext is part of LINQ to SQL, and the ORM table-mapped class is also part of LINQ to SQL.

Listing 1: The WCF service implementation based on the contract in Listing 2 requests a list of customers with LINQ to SQL and refines the list based on a LINQ query (shown).

' NOTE: If you change the class name "Service1" here, you must
' also update the reference to "Service1" in Web.config and in
' the associated .svc file.
Public Class Service1
   Implements IService1

   Public Sub New()
   End Sub

   Public Function GetCustomer(ByVal CustomerID As String) _
      As Customer _
      Implements IService1.GetCustomer

      Dim northwind As Northwind = New Northwind

      Dim cust = (From customer In northwind.Customers _
                  Where customer.CustomerID = CustomerID _
                  Select customer).First()
      Return cust

   End Function
End Class

Listing 2: Defines the ServiceContract, the custom DataContext (for LINQ to SQL), and the table-mapped ORM (the Customer class).

Imports System.Data.Linq
Imports System.Data.Linq.Mapping

<ServiceContract()> _
Public Interface IService1

   <OperationContract()> _
   Function GetCustomer(ByVal CustomerID As String) As Customer

End Interface

Public Class Northwind
   Inherits DataContext

   Private Shared ReadOnly connectionString As String

   Public Sub New()
      MyBase.New(connectionString)
   End Sub

   Public ReadOnly Property Customers() As List(Of Customer)
      Get
         Return Me.GetTable(Of Customer)().ToList()
      End Get
   End Property

End Class

<DataContract()> _
<Table(Name:="Customers")> _
Public Class Customer

   Private _customerID As String
   <DataMember()> _
   <Column(IsPrimaryKey:=True)> _
   Public Property CustomerID() As String
      Get
         Return _customerID
      End Get
      Set(ByVal Value As String)
         _customerID = Value
      End Set
   End Property

   Private _companyName As String
   <DataMember()> _
   <Column()> _
   Public Property CompanyName() As String
      Get
         Return _companyName
      End Get
      Set(ByVal Value As String)
         _companyName = Value
      End Set
   End Property

   Private _contactName As String
   <DataMember()> _
   <Column()> _
   Public Property ContactName() As String
      Get
         Return _contactName
      End Get
      Set(ByVal Value As String)
         _contactName = Value
      End Set
   End Property

   Private _contactTitle As String
   <DataMember()> _
   <Column()> _
   Public Property ContactTitle() As String
      Get
         Return _contactTitle
      End Get
      Set(ByVal Value As String)
         _contactTitle = Value
      End Set
   End Property

   Private _address As String
   <DataMember()> _
   <Column()> _
   Public Property Address() As String
      Get
         Return _address
      End Get
      Set(ByVal Value As String)
         _address = Value
      End Set
   End Property

   Private _city As String
   <DataMember()> _
   <Column()> _
   Public Property City() As String
      Get
         Return _city
      End Get
      Set(ByVal Value As String)
         _city = Value
      End Set
   End Property

   Private _region As String
   <DataMember()> _
   <Column()> _
   Public Property Region() As String
      Get
         Return _region
      End Get
      Set(ByVal Value As String)
         _region = Value
      End Set
   End Property

   Private _postalCode As String
   <DataMember()> _
   <Column()> _
   Public Property PostalCode() As String
      Get
         Return _postalCode
      End Get
      Set(ByVal Value As String)
         _postalCode = Value
      End Set
   End Property

   Private _country As String
   <DataMember()> _
   <Column()> _
   Public Property Country() As String
      Get
         Return _country
      End Get
      Set(ByVal Value As String)
         _country = Value
      End Set
   EndProperty

   Private _phone As String
   <DataMember()> _
   <Column()> _
   Public Property Phone() As String
      Get
         Return _phone
      End Get
      Set(ByVal Value As String)
         _phone = Value
      End Set
   End Property

   Private _fax As String
   <DataMember()> _
   <Column()> _
   Public Property Fax() As String
      Get
         Return _fax
      End Get
      Set(ByVal Value As String)
         _fax = Value
      End Set
   End Property

End Class

Unit Testing with Service Stubs or Mock Types

Suppose your project also is using a distributed development model and the developers are learning LINQ and WCF, so the service is not ready even though you are ready to implement the service consumer. In this scenario, all you would need is an agreement on the definition of Customer and the service to be provided; that is, request a customer by ID and the service returns the right customer.

To insulate you from dependence on the service provider, you will need to implement a factory, an interface that describes the service's interface, a concrete type, and a mock type. Both the concrete and mock types have to realize—inherit from—the interface. The CustomerServicesFactory simply tries to return an instance of the concrete type, and if an exception occurs, it returns the mock type.

Listing 3 contains a console application that represents the client. I was bored, so the code actually uses a generic delegate to format the Customer—I was showboating a tad. All the code really does is request a Customer and send the customer state to the console.

Listing 3: A console application that sends the Customer state to the console—a little overkill using a generic delegate, but it was fun.

Imports UnitTestingWithMockTypes.ServiceReference1
Imports System.Runtime.CompilerServices
Imports System.Reflection
Imports System.Text

Module Module1

   Sub Main()

   Dim Format As Func(Of Customer, String) = _
      Function(cust) (From prop In cust.GetType(). _
      GetProperties()).Aggregate( _
   New StringBuilder(), Function(builder, p) _
      builder.AppendFormat("{0, -20}: {1}{2}", p.Name, _
      p.GetValue(cust, Nothing), Environment.NewLine), _
      Function(builder) builder.ToString())


   Dim factory As ICustomerServices = _
      CustomerServicesFactory.Create()

   Console.WriteLine(Format(factory.GetCustomer("ALFKI")))
   Console.ReadLine()

   End Sub

   <Extension()> _
   Function ToFormattedString(ByVal cust As Customer)

      Return (From prop _
         In cust.GetType().GetProperties()).Aggregate( _
         New StringBuilder(), Function(builder, p) _
            builder.AppendFormat("{0}: {1}{2}", p.Name, _
            p.GetValue(cust, Nothing), Environment.NewLine), _
            Function (builder) builder.ToString())

   End Function
End Module

Listing 4 contains an implementation of the Factory pattern. Factory patterns exist to instantiate objects based on rules. In the example, the Factory defaults to the mock service object in the event of an exception. Listing 5 defines the interface that is part of mock typing (or the service stub enterprise pattern). Notice that the interface mirrors the WCF service.

Listing 4: Using an implementation of the Factory pattern to instantiate an instance of the concrete (or mock, as hardcoded to demonstrate) object as needed.

Public Class CustomerServicesFactory

   Public Shared Function Create() As ICustomerServices
      Try
         Throw New Exception
         Return New CustomerServices
      Catch ex As Exception
         Return New MockCustomerServices
      End Try
   End Function

End Class

Listing 5: This interface is part of using mock types; declare the interface to instantiate a class that inherits from it.

Public Interface ICustomerServices

   Function GetCustomer(ByVal customerID As String) As Customer
End Interface

Listing 6 implements the mock service with hard-coded Customer data, and Listing 7 actually consumes the service.

Listing 6: The mock class implements the interface but returns what is essentially test data.

Public Class MockCustomerServices
   Implements ICustomerServices

   Public Function GetCustomer(ByVal customerID As String) _
      As Customer _
      Implements ICustomerServices.GetCustomer

      Dim cust As Customer = New Customer
      cust.CustomerID   = "ALFKI"
      cust.CompanyName  = "Alfreds Futterkiste"
      cust.ContactName  = "Paul Kimmel"
      cust.ContactTitle = "Sales Representative"
      cust.Address      = "Obere Str. 57"
      cust.City         = "Berlin"
      cust.Region       = ""
      cust.PostalCode   = "12209"
      cust.Country      = "Germany"
      cust.Phone        = "030-0074321"
      cust.Fax          = "030-0076541"
      Return cust

   End Function
End Class

Listing 7: Represents the concrete service wrapper, which actually interacts with the service.

Public Class CustomerServices
   Implements ICustomerServices

   Public Function GetCustomer(ByVal customerID As String) _
      As Customer _
      Implements ICustomerServices.GetCustomer

      Dim client As Service1Client = New Service1Client
      Return client.GetCustomer(customerID)

   End Function

End Class

Mock typing to this degree is pretty easy once you figure out the nuts and bolts. I wouldn't use this strategy for everything, but I would use it for services, facades, 3rd party and legacy sub-systems, externally provided parts of your solution, and perhaps code that is going to take a while to make available.

Summary

A huge part of software development is momentum. If you get stuck waiting on code that has been outsourced or rickety old 3rd party or legacy sub-systems, it can kill your momentum. Implement a mock type, code against the mock type, and keep on trucking.

Mock types will facilitate test-driven development, delineations, and dependencies on outsourced parts of your project, and undependable legacy and 3rd party systems.

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his upcoming book LINQ Unleashed for C# due in July 2008. Paul Kimmel is an Application Architect for EDS. You may contact him for technology questions at pkimmel@softconcepts.com.

Lansing is having a free Day of .NET training at Lansing Community College on June 21st. Check out the web site for details. The group likes to think of it as a poor man's TechEd (because it's free), but the content will be excellent.

Copyright © 2008 by Paul T. Kimmel. All Rights Reserved.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds