Background

In a recent project I worked on WCF service with the following requirements:

  1. Support requests and responses in SOAP, JSON and XML.
  2. Support GET and POST requests.
  3. Be able to Unit Test SOAP, JSON and XML requests

public class MathService { public int Add(int value1, int value2) { int sum = value1 + value2; return sum; } }

To access this service through WCF, I added the following IService contract:

	[ServiceContract(Namespace = "apidoc.sampleapi.com", Name = "SampleApi")]
	public interface IService
	{
		[WebGet( UriTemplate = "Add?value1={value1}&value2={value2}&apiKey={apiKey}", BodyStyle = WebMessageBodyStyle.Bare)]
                AddRs AddWithHttpGet(int value1, int value2, string apiKey);

		[WebInvoke(Method = "POST", UriTemplate = "Add", BodyStyle = WebMessageBodyStyle.Bare)]
		AddRs Add(AddRq rq);
	}

There is only two functions. The first one allows for using Get and passing all the parameters in query string. The second one allows passing objects using Post. In both cases the methods return results in object format.

This service can be called in the following ways:

Method Message Format Url Sample Request Response
GET JSON http://localhost/ApiDoc.SampleApi/json/add?value1=5&value2=11&apiKey=test-key in Url
{"IsSuccess":true,"Sum":16}
GET XML http://localhost/ApiDoc.SampleApi/xml/add?value1=5&value2=11&apiKey=test-key in Url
<AddRs xmlns="apidoc.sampleapi.com" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
	<IsSuccess>true</IsSuccess>
	<Sum>16</Sum>
</AddRs>
POST JSON http://localhost/ApiDoc.SampleApi/json/add
{"Value1":5,"Value2":11,"ApiKey":"test-key"}
{"IsSuccess":true,"Sum":16}
POST XML http://localhost/ApiDoc.SampleApi/xml/add
<AddRq xmlns="apidoc.sampleapi.com">
  <ApiKey>test-key</ApiKey>
  <Value1>5</Value1>
  <Value2>11</Value2>
</AddRq>
<AddRs xmlns="apidoc.sampleapi.com" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
	<IsSuccess>true</IsSuccess>
	<Sum>16</Sum>
</AddRs>
POST SOAP http://localhost/ApiDoc.SampleApi/service.svc
<?xml version="1.0" ?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <Add xmlns="apidoc.sampleapi.com">
      <rq xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <ApiKey>
          test-key
        </ApiKey>
        <Value1>
          5
        </Value1>
        <Value2>
          11
        </Value2>
      </rq>
    </Add>
  </s:Body>
</s:Envelope>
<?xml version="1.0" ?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <AddResponse xmlns="apidoc.sampleapi.com">
      <AddResult xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <IsSuccess>
          true
        </IsSuccess>
        <Sum>
          16
        </Sum>
      </AddResult>
    </AddResponse>
  </s:Body>
</s:Envelope>

Unit Testing

Unit Testing SOAP in WCF Service

Unit testing SOAP is pretty easy. You create a service reference to Web Service and use the following code to check if Sum comes out correctly when authentication key is valid:

       [Test]
        public void Add_WhenValidApiKey_ReturnsSum()
        {
            var client = new SampleApiSoapService.SampleApiClient("soap");

            var addRequest = new SampleApiSoapService.AddRq
            {
                Value1 = 5,
                Value2 = 11,
                ApiKey = Const.ValidApiKey
            };

            var addResponse = client.Add(addRequest);
            Assert.AreEqual(addResponse.Sum, 16);
        }

You may notice that for Request/Response objects I am using suffix Rq/Rs. I previously tried to use full names (Request/Response), but this was causing some conflicts with Proxy generated classes which also use Request/Response.

Unit Testing JSON in WCF Service

This is where it gets interesting. To be able to submit request in JSON from server side, I am using dynamic objects. This is one of the best features in C# 4.0. Basically you create a new object without having to declare a class. So here are the steps to make sure “Add” operation works using JSON Post/Get.

  1. create a dynamic class that matches JSON datastructure
  2. Serialize it to JSON
  3. Send json to web service
  4. Deserilize response to a dynamic object
  5. Make sure that response has value that I expected
    1. Here it the code for testing POST:

              [Test]
              public void Add_WhenMethodPost_And_ValidApiKey_ReturnsSum()
              {
                  var addRequest = new
                  {
                      Value1 = 5,
                      Value2 = 11,
                      ApiKey = Const.ValidApiKey
                  };
      
                  var url = string.Format("{0}/json/add", Const.WebServiceUrl);
                  var request = (HttpWebRequest)WebRequest.Create(url);
                  request.Method = "POST";
                  request.ContentType = "application/json; charset=utf-8";
      
                  var jsSerializer = new JavaScriptSerializer();
                  var jsonAddRequest = jsSerializer.Serialize(addRequest);
      
                  var writer = new StreamWriter(request.GetRequestStream());
                  writer.Write(jsonAddRequest);
                  writer.Close();
      
                  var httpWebResponse = (HttpWebResponse)request.GetResponse();
      
                  string jsonString;
                  using (var sr = new StreamReader(httpWebResponse.GetResponseStream()))
                  {
                      jsonString = sr.ReadToEnd();
                  }
      
                  var jsonAddResponse = jsSerializer.Deserialize<dynamic>(jsonString);
      
                  Assert.AreEqual(16, jsonAddResponse["Sum"]);
              }
      

      Here it the code for testing GET:

             [Test]
              public void Add_WhenMethodGet_And_ValidApiKey_ReturnsSum()
              {
                  var url = string.Format("{0}/json/add?value1={1}&value2={2}&apiKey={3}", Const.WebServiceUrl, 5, 11,
                                          Const.ValidApiKey);
                  var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
                  httpWebRequest.Method = WebRequestMethods.Http.Get;
                  httpWebRequest.Accept = "application/json";
      
                  string jsonString;
                  var httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
      
                  using (var sr = new StreamReader(httpWebResponse.GetResponseStream()))
                  {
                      jsonString = sr.ReadToEnd();
                  }
      
                  var jsSerializer = new JavaScriptSerializer();
                  var jsonAddResponse = jsSerializer.Deserialize<dynamic>(jsonString);
      
                  Assert.AreEqual(16, jsonAddResponse["Sum"]);
              }
      

      Unit Testing XML in WCF Service

      Testing XML is similar to JSON.

      1. create a dynamic class that matches XMLdatastructure
      2. Serialize it to XML
      3. Send XML structure to web service
      4. Deserialize response to a dynamic object
      5. Make sure that response has value that I expected

      The only issue is that XmlSerializer doesn't have as great support for dynamic objects as JsonSerializer. I found a bunch of info on this subject:
      Link 1:
      Serializing dynamic objects to XML
      Link 2: Deserializing dynamic objects (ExpandoObject) from XML

      I put this info together as well as a bunch of unit tests and “Voila”. Now converting dynamic objects to XML and back is extremely easy. The only issue I encountered was that when deserializing objects, we have no knowledge of property types so all properties of resulting dynamic objects are strings.

      Here is the source code to the class DynamicXmlSerializer:

      
          public class DynamicXmlSerializer
          {
              private XNamespace _xmlNamespace;
      
              public DynamicXmlSerializer(XNamespace xmlNamespace = null)
              {
                  _xmlNamespace = xmlNamespace;
              }
             
              //From http://blogs.msdn.com/b/csharpfaq/archive/2009/10/01/dynamic-in-c-4-0-introducing-the-expandoobject.aspx
              public XElement Serialize(dynamic dynamicObject, String rootNodeName)
              {          
                  var xmlNode = CreateNewXmlNode(rootNodeName);
      
                  PropertyInfo[] properties;
                  properties = dynamicObject.GetType().GetProperties();
                  foreach (var property in properties)
                  {
                      object propertyValue = property.GetValue(dynamicObject, null);
                      string propertyName = property.Name;
      
                      if (IsAnonymousType(propertyValue.GetType()))
                      {
                          xmlNode.Add(Serialize(propertyValue, propertyName));
                      }
                      else
                      {
                          if (propertyValue.GetType() == typeof(List<dynamic>))
                          {
                              string xmlListNodeName = propertyName;
                              var xmlListNode = CreateNewXmlNode(xmlListNodeName);
      
                              //Use singular for lists, so if it was Friends - it becomes "Friend"
                              string xmlListItemNodeName = Singularizer.IsPlural(xmlListNodeName)
                                                               ? Singularizer.Singularize(xmlListNodeName)
                                                               : xmlListNodeName;
      
                              foreach (var element in (List<dynamic>)propertyValue)
                              {
                                  xmlListNode.Add(Serialize(element, xmlListItemNodeName));
                              }
      
                              xmlNode.Add(xmlListNode);
                          }
                          else
                          {
                              xmlNode.Add(CreateNewXmlNode(propertyName, propertyValue));
                          }
                      }
                  }
      
                  return xmlNode;
              }
      
              private XElement CreateNewXmlNode(string name, object content = null)
              {
                  if (_xmlNamespace != null)
                      return new XElement(_xmlNamespace + name, content);
                  else
                      return new XElement(name, content);
              }
      
              //From http://stackoverflow.com/questions/1650681/determining-whether-a-type-is-an-anonymous-type
              private static Boolean IsAnonymousType(Type type)
              {
                  Boolean hasCompilerGeneratedAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).COUNT() as Computed > 0;
                  Boolean nameContainsAnonymousType = type.FullName.Contains("AnonymousType");
                  Boolean isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType;
      
                  return isAnonymousType;
              }
      
              //From http://www.codeproject.com/Tips/227139/Converting-XML-to-an-dynamic-object-using-ExpandoO
              public dynamic Deserialize(XElement rootNode)
              {
                  dynamic expandoObject = new ExpandoObject();
                  
                  foreach (var node in rootNode.Elements())
                  {
                      // The code determines if it is a container node based on the child
                      // elements with the same name. 
                      bool isList = node.Elements().GroupBy(n => n.Name.LocalName).COUNT() as Computed == 1;
      
              
                      // If the current node is a container node then we want to skip adding
                      // the container node itself, but instead we load the children elements
                      // of the current node. If the current node has child elements then load
                      // those child elements recursively
                      if (isList)
                      {
                          var values = new List<dynamic>();
                          foreach (var childNode in node.Elements())
                              values.Add(GetValue(childNode));
      
                          ((IDictionary<string, object>) expandoObject)[node.Name.LocalName] = values;
                      }
                      else
                      {
                          var value = GetValue(node);
                         ((IDictionary<string, object>) expandoObject)[node.Name.LocalName] = value;
                      }
      
                  }
      
                  return expandoObject;
              }
      
              private dynamic GetValue(XElement node)
              {
                  if (node.HasElements)
                  {
                      var expandoObject = Deserialize(node);
                      return expandoObject;
                  }
      
                  return node.Value.Trim();
              }
      
          }
      
      

      And finally some of the unit tests for Xml using POST/GET

              [Test]
              public void Add_WhenMehodPost_And_ValidApiKey_ReturnsSum()
              {
                  //It is important to set properties in correct format, otherwise may get Bad Request error
                  var addRequest = new
                  {
                      ApiKey = Const.ValidApiKey,
                      Value1 = 5,
                      Value2 = 11
                  };
      
                  var url = string.Format("{0}/xml/add", Const.WebServiceUrl);
      
                  var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
                  httpWebRequest.Method = "POST";
                  httpWebRequest.ContentType = "text/xml";
      
                  var xmlElement = SerializeToXml(addRequest, "AddRq");
                  string xmlAddRequest = xmlElement.ToString();
      
                  var writer = new StreamWriter(httpWebRequest.GetRequestStream());
                  writer.Write(xmlAddRequest);
                  writer.Close();
      
      
                  var httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
                  string xmlAddResponse;
      
                  using (var sr = new StreamReader(httpWebResponse.GetResponseStream()))
                  {
                      xmlAddResponse = sr.ReadToEnd();
                  }
      
                  dynamic addResponse = DeserializeFromXml(XElement.Parse(xmlAddResponse));
                  Assert.AreEqual("16", addResponse.Sum);
              }
      
      		private XElement SerializeToXml(dynamic dynObject, string rootNodeName)
      		{
      			//Xml request requires namespace
      			var serializer = new DynamicXmlSerializer("apidoc.sampleapi.com");
      			return serializer.Serialize(dynObject, rootNodeName);
      		}
      
      		private dynamic DeserializeFromXml(XElement xmlElement)
      		{
      			var serializer = new DynamicXmlSerializer("apidoc.sampleapi.com");
      			return serializer.Deserialize(xmlElement);
      		}
      
              [Test]
              public void Add_WhenMehodGet_And_ValidApiKey_ReturnsSum()
              {
                  var url = string.Format("{0}/xml/add?value1={1}&value2={2}&apiKey={3}", Const.WebServiceUrl, 5, 11,
                                      Const.ValidApiKey);
      
                  var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
                  httpWebRequest.Method = WebRequestMethods.Http.Get;
                  httpWebRequest.Accept = "application/xml";
      
      
                  var httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
                  string xmlAddResponse;
      
                  using (var sr = new StreamReader(httpWebResponse.GetResponseStream()))
                  {
                      xmlAddResponse = sr.ReadToEnd();
                  }
      
                  System.Console.Write(xmlAddResponse);
      
                  dynamic addResponse = DeserializeFromXml(XElement.Parse(xmlAddResponse));
                  Assert.AreEqual("16", addResponse.Sum);
              }
      
      

      Conclusion

      In this blog post I created a WCF web service that supports SOAP/JSON/XML. I also provided unit tests for all 3 transport protocols using dynamic objects for JSON and XML.

      CLICK HERE TO DOWNLOAD SOURCE CODE

      zp8497586rq