binary dreams

a world of 1s and 0s

Give your QA a stub while you develop your service

In my development career I have often needed to provide a stub of my service. In this case I develop

In my development career I have often needed to provide a stub of my service to help test an application.

I originally started to hardcode data to return specific responses. I realised this was wrong a few years ago when I had to continually change customer identifiers to suit the testing taking place. This was wrong and so I changed my strategy from then on.

In this post I will go through what a stub should and should NOT do, show how my reusable stub class works and provide an example of it.


The stub rules

A stub should:

  • Provide the ability to return a response that is not easily repeatable.
  • Enable it to be used in a larger test with other applications.
  • Save the developer and/or QA doing all sorts of complex data setup that may be required. I.e. Database.
  • Allow anyone to set the response of the service method to whatever is possible for that operation.
  • Be lean. It shouldn't be time-consuming to maintain.
  • NOT tightly couple specific data requests to specific responses. This only increases the maintenance for the developer to change it when required.

Do bare in mind this solution does limit the stub by returning the same response to whatever called it after whoever set it. For example, if two people want different responses then they need to negotiate their time testing of the service.


The response object we want to stub

We want to set the main method to return this response object.

    [DataContract]     public class CalculateResponse     {         [DataMember]         public ValueType TypeOfValue { getset; }         [DataMember]         public object Result { getset; }     }

    public enum ValueType
    {
        Byte,
        Short,
        ShortUnsigned,
        Integer,
        IntegerUnsigned,
        Double,
        Float
    }


The stub base class 

So to make it easier to reuse I needed two reusable methods to call for a Set method and the main method.

The SetResponse will take whatever object the TResponse is and the method name, serialise the TResponse object into xml and save it as the method name, for example CalculatorServiceStub_Calculate.xml. If the XML file already exists the code will overwrite it.

public void SetResponse<TResponse>(TResponse response, string methodName)     where TResponse : classnew() {     if (response == null)         throw new ArgumentNullException("request");     if (string.IsNullOrWhiteSpace(methodName))         throw new ArgumentException("Value cannot be null, empty or have whitespace."
"methodName");     try     {         var xml = CreateXML<TResponse>(response);         SaveXML(methodName, xml.ToString());     }     catch (Exception exception)     {         // log the exception         throw;     } } private StringBuilder CreateXML<TResponse>(TResponse response)      where TResponse : classnew() {     var xml = new StringBuilder();     var serialiser = new XmlSerializer(response.GetType());     var writerSettings = new XmlWriterSettings();     using (XmlWriter xmlWriter = XmlWriter.Create(xml, writerSettings))     {         serialiser.Serialize(xmlWriter, response);     }     return xml; } private void SaveXML(string methodName, string xml) {     string fullPath = StubXmlLocation(methodName);     using (Stream stream = new FileStream(fullPath, FileMode.Create, FileAccess.Write))     {         XmlDocument document = new XmlDocument();         document.LoadXml(xml);         document.Save(stream);     } }

The xml filename is created using the StubXmlLocation method.

        private string StubXmlLocation(string method)
        {
            var setting = ConfigurationManager.AppSettings["StubXmlOutputLocation"];
            string path = string.IsNullOrWhiteSpace(setting) ? @".\" : setting;
 
            string className = this.GetType().Name;
 
            return string.Format("{0}{1}_{2}.xml", path, className, method);
        }

The RetrieveResponse will take the method name. The Xml will be loaded from the expected filename and serialised into the TResponse object.

public TResponse RetrieveResponse<TResponse>(string methodName)     where TResponse : classnew() {     if (string.IsNullOrWhiteSpace(methodName))         throw new ArgumentException("Value cannot be null, empty or have whitespace."
"methodName");     TResponse response = new TResponse();     try     {         XDocument xmlDocument = XDocument.Load(StubXmlLocation(methodName));         using (TextReader textReader = new StringReader(xmlDocument.ToString()))         {             var serialiser = new XmlSerializer(response.GetType());             response = (TResponse)serialiser.Deserialize(textReader);         }     }     catch (Exception exception)     {         // log the exception         throw;     }     return response; }

Using both of these public methods works incredibly well because any changes to the service contract will be reflected the next time the SetResponse method is called and the new XML saved.

The saved xml file contents could look like this:

<?xml version="1.0" encoding="utf-16"?>
<CalculateResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <TypeOfValue>Integer</TypeOfValue>
  <Result xsi:type="xsd:int">10</Result>
</CalculateResponse>


The Service Stub

Now in your service you would have something like this using the two methods. Now your stubbing is so easy!

public class CalculatorServiceStub : StubBaseICalculatorServiceStub {     public CalculateResponse Calculate(CalculateRequest request)     {         if (request == null)             throw new ArgumentNullException("request");         return RetrieveResponse<CalculateResponse>("Calculate");     }     public void SetCalculate(SetCalculateRequest request)     {         if (request == null)             throw new ArgumentNullException("request");         SetResponse<CalculateResponse>(request.Request, "Calculate");     }

}


I have attached the example Visual Studio 2012 project as a ZIP file. CalculatorService.zip (79KB)

Get stubbing!