February 2010

Volume 25 Number 02

Test Run - WCF Service Testing with Sockets

By Dr. James McCaffrey | February 2010

In this month’s column I am joined by Carlos Figueira, a senior software development engineer in Test on the Windows Communication Foundation (WCF) team. With his help I intend to show you how to test WCF services using a network socket-based approach.

A good way for you to see where I’m headed is to examine the screenshots in Figures 1, 2 and 3. Figure 1 shows a WinForm application that hosts a simple-but-representative WCF service named MathService. Behind the scenes, MathService contains a single operation named Sum that accepts two values of type double, then computes and returns their sum.

image: WCF MathService Service Under Test

Figure 1 WCF MathService Service Under Test

Figure 2 shows a typical WCF ASP.NET Web application client that accepts two values from the user, sends those two values to the MathService service, fetches the response from the service, and displays the result in a ListBox control.

image: Typical WCF ASP.NET Client

Figure 2 Typical WCF ASP.NET Client

Figure 3 shows a console application test harness that performs functional verification of the MathService service. The test harness sends SOAP messages directly to MathService using a network socket, accepts the response from the service, and compares an expected result with the actual result to determine a pass or fail. In test case #001, the test harness sends 3.4 and 5.7 to the WCF service under test and receives the expected value 9.1. Test case #002 is a deliberate, spurious failure just for demonstration purposes.

In the sections that follow, I first briefly describe the WCF service under test shown in Figure 1 so you’ll understand which factors are relevant when constructing WCF socket-based test automation. Next I briefly explain the demonstration Web client to give you some insight into when socket-based testing is more appropriate than alternative techniques. Then I explain in detail the code that created the test harness so that you will be able to adapt the technique I present here to meet your own needs. This article assumes you have intermediate-level C# coding skills.

image: WCF Test Harness Run

Figure 3 WCF Test Harness Run

The WCF Service Under Test

I used Visual Studio 2008 to create the WCF service under test. One of the neat features about WCF services is that they can be hosted in many different types of applications. I decided to host the WCF MathService service in a WinForm application, but the techniques I present in this article work with any type of WCF host.

After creating an empty WinForm, I added two Button controls and one ListBox control. Then, just below the Form definition, I added the simple code required to declare a WCF service:

[ServiceContract]
   public interface IMathService {
   [OperationContract]
     double Sum(double x, double y);
   }

The ServiceContract attribute applied to an interface generates all the code needed for a WCF inteface. If you’re moving to WCF from Web services, you can think of an OperationContract attribute as being analogous to the WebMethod attribute. Implementing the WCF service is simple:

public class MathService : IMathService  {
  public double Sum(double x, double y) {
    double answer = x + y;
    return answer;
  }
}

With my WCF plumbing in place, I added a reference to System.ServiceModel.dll, the .NET assembly that houses WCF functionality, to my project. Then I added using statements to the two key .NET namespaces contained within the assembly needed for my WCF service:

using System.ServiceModel;
using System.ServiceModel.Description;

The ServiceModel namespace holds the ServiceHost class and several classes that define WCF bindings. The Description namespace holds the ServiceMetadataBehavior class I use to publish information about my WCF service. Next I added service instantiation logic to the Button1 control event handler:

try {
  string address = 
    "https://localhost:8000/MyWCFMathService/Service";
  Uri baseAddress = new Uri(address);
  serviceHost = 
    new ServiceHost(typeof(MathService), baseAddress);
  serviceHost.AddServiceEndpoint(typeof(IMathService),
    new WSHttpBinding(SecurityMode.None), "MathService");
  . . .

The key factor to note here is that I create a WCF endpoint using WSHttpBinding. WCF bindings are a collection that includes information about secutity and reliabilty settings, transport protocol and encoding type. The WSHttpBinding is an excellent general purpose binding for non-duplex communication. By default, WSHttpBinding uses encrypted transmission, but here I specify SecurityMode.None so that you can more easily see request-response data.

Next I add code to make my service visible to clients through the Add Service Reference mechanism:

ServiceMetadataBehavior smb = 
  new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
serviceHost.Description.Behaviors.Add(smb);
. . .

Now I’m ready to add code that starts up the WCF service:

serviceHost.Open();
int count = 0;
while (serviceHost.State != 
       CommunicationState.Opened && 
       count < 50) {
  System.Threading.Thread.Sleep(100);
  ++count;
}
. . .

I use a simple but effective way to detect a service hang at startup by going into a delay loop of 0.1 seconds per delay, up to a maximum of 50 delays. After the delay loop terminates, either the WCF service has started or the maximum number of delays has been reached:

if (serviceHost.State == CommunicationState.Opened)
  listBox1.Items.Add("WCF MathService is started");
else
  throw new Exception(
    "Unable to start WCF MathService in a timely manner");

I’m taking many shortcuts you wouldn’t take when writing production code, such as not checking to see if the serviceHost object is already open before attempting to open it. The logic in the Button2 control event handler closes the WCF MathService, and I use that same pattern to start the service:

int count = 0;
serviceHost.Close();
while (serviceHost.State != 
       CommunicationState.Closed && 
       count < 50) {
  System.Threading.Thread.Sleep(100);
  ++count;
}

A Typical Web Application Client

I used Visual Studio 2008 to create the WCF Web application client shown in Figure 2. I started by clicking File | New | Web Site. In the new Web site dialog box, I targeted the Microsoft .NET Framework 3.5, selected the Empty Web Site template, chose a File System location using the C# language, and named my project WCFMathServiceWebClient.

Next, in Solution Explorer, I right-clicked on my project and selected the Add New Item option from the context menu. In the resulting dialog box, I selected the Web Form item. I deselected the “Place code in separate file” option so I could place all my application code in a single file.

After adding the Web Form, I added server-side control tags to create the very simple UI of the Web application:

<asp:Label ID="Label1" runat="server" 
  Text="Enter first number: "/>
<asp:TextBox ID="TextBox1" runat="server" /><p />
<asp:Label ID="Label2" runat="server" 
  Text="Enter second number: "/>
<asp:TextBox ID="TextBox2" runat="server" /><p />
<asp:Button ID="Button1" runat="server" Text="Compute Sum" 
  onclick="Button1_Click" /><p />
<asp:ListBox ID="ListBox1" runat="server" />

Next, I hit the F5 key to instruct Visual Studio to build the application and prompt me to allow the automatic generation of a Web.config file for my project. After I OK’d the creation of the Web.config file, I started the WCF MathService service so my client project would be able to see the service.

I right-clicked on the client project in Solution Explorer and selected the Add Service Reference option from the context menu. In the Add Service Reference dialog box, I entered the location of my WCF service (https://localhost:8000/ MyWCFMathService/ Service) and clicked the Go button. Because my WCF service is running and the service publishes metadata about itself, the Visual Studio tool will find the service. I renamed the service namespace from the default ServiceReference1 to WCFMathServiceReference.

Behind the scenes, Visual Studio adds information about the WCF service to the Web.config file by creating a <system.serviceModel> entry. That entry contains an <endpoint> element with attribute binding=”wsHttpBinding” so that the Web application client knows how to communicate with the WCF service. In design view, I double-clicked on the Button1 control to register its event handler and added logic to access the service:

try {
  WCFMathServiceReference.MathServiceClient sc =
    new WCFMathServiceReference.MathServiceClient();
  double x = double.Parse(TextBox1.Text);
  double y = double.Parse(TextBox2.Text);
  double sum = sc.Sum(x, y);
  . . . 
  ListBox1.Items.Add(
    "The response from the WCF service is " + sum);

The name of the class that holds the Sum operation is MathServiceClient—in other words, the name of the class (MathService) derived from the WCF contract interface (IMathService), with “Client” appended.

The Test Harness

I also used Visual Studio 2008 to create a C# console application project named WCFTestHarness. At the very top of the Visual Studio-generated Program.cs file I added these using statements:

using System;
using System.Text; 
using System.Net; 
using System.Net.Sockets;

I need classes in the System.Text namespace to convert text to byte arrays because TCP works at the byte level. I need classes in the System.Net namespace to create objects that are abstractions of IP addresses. And I need the System.Net.Sockets namespace to create a socket object that performs the actual send and receive operations. I added a brief logging message to the Main method and then set up my test case data as an array of strings:

namespace WCFTestHarness {
  class Program {
    static void Main(string[] args) {
      try {
        Console.WriteLine(
        "\nBegin WCF MathService testing via sockets run");

        string[] testCases = new string[] {
          "001,3.4,5.7,9.1",
          "002,0.0,0.1,1.0",
          "003,6.7,6.7,13.4"
        };
        . . .

Each test case data string contains four comma-delimited fields: a test case ID, two inputs to the Sum operation, and an expected response value. I use my test case data to create the main test harness processing loop:

foreach (string testCase in testCases) {
  Console.WriteLine("\n============\n");
  string[] tokens = testCase.Split(',');
  string caseID = tokens[0];
  string input1 = tokens[1];
  string input2 = tokens[2];
  string expected = tokens[3];
  . . .

I use the Split method to break each string into its four fields.

Next comes one of the key parts of sending input to a WCF service using sockets. I create a SOAP message as shown in Figure 4.

Figure 4 SOAP Message for testing the WCF service

string soapMessage = "<s:Envelope xmlns:s='https://www.w3.org/2003/05/soap-envelope'";
soapMessage += " xmlns:a='https://www.w3.org/2005/08/addressing'><s:Header>";
soapMessage += "<a:Action s:mustUnderstand='1'>https://tempuri.org/IMathService/Sum</a:Action>";
soapMessage += "<a:MessageID>urn:uuid:510b1790-0b89-4c85-8015-d1043ffeea14</a:MessageID>";
soapMessage += "<a:ReplyTo><a:Address>https://www.w3.org/2005/08/addressing/anonymous</a:Address>";
soapMessage += "</a:ReplyTo><a:To s:mustUnderstand='1'>";
soapMessage += "https://localhost:8000/MyWCFMathService/Service/MathService</a:To></s:Header>";
soapMessage += "<s:Body><Sum xmlns='https://tempuri.org/'>";

soapMessage += "<x>" + input1 + "</x>" + "<y>" + input2 + "</y>";

soapMessage += "</Sum></s:Body></s:Envelope>";

Notice that my test case input values, input1 and input2, are embedded into the SOAP message inside a <Sum> element near the end of the message. But how did I determine this not-so-simple message structure?

There are several ways you can determine the required SOAP message structure and data for a WCF service. The easiest approach, and the one I used, is to use a network traffic examination tool such as netmon or Fiddler to capture data while you exercise a client application. In other words, with my WCF service running, and a traffic capture tool also running (I used Fiddler), I launched the Web application client program shown in Figure 2 and used the client to send a request to the WCF service. The network traffic capture tool showed me the SOAP message that was sent from the client to the WCF service. I used that information to craft the SOAP message in my test harness.

With my SOAP message constructed, next I displayed my test case input as expected data to the shell:

Console.WriteLine(
  "Test Case   : " + caseID);
Console.WriteLine(
  "Input       : <s:Envelope..." + "<x>" +
  input1 + "</x>" + "<y>" + input2 + 
  "</y>...</s:Envelope>");
Console.WriteLine("Expected    : " + expected);

Then I set up the IP address of the target WCF MathService service under test:

string host = "localhost";
   IPHostEntry iphe = Dns.Resolve(host);
   IPAddress[] addList = iphe.AddressList;
   EndPoint ep = new IPEndPoint(addList[0], 8000);

The Dns.Resolve method returns a list of IP addresses (there may be more than one) associated with a particular host machine name as an IPHostEntry object. The IPHostEntry object has an AddressList property that is an array of IPAddress objects. An EndPoint object consists of an IPAddress object plus a port number. Now I can create my socket:

Socket socket = new Socket(AddressFamily.InterNetwork,
  SocketType.Stream, ProtocolType.Tcp);
socket.Connect(ep);
if (socket.Connected)
  Console.WriteLine(
    "Connected to socket at " + ep.ToString());
else
  Console.WriteLine(
    "Error: Unable to connect to " + ep.ToString());

Socket objects implement the Berkeley sockets interface, which is an abstraction mechanism for sending and receiving network traffic. The first argument to the Socket constructor specifies the addressing scheme. Here I use InterNetwork, which is ordinary IPv4.

The second argument to the Socket constructor specifies which of six possible socket types you want to create. Stream indicates a TCP socket. The other common type is Dgram, which is used for UDP (User Datagram Protocol) sockets.

The third argument to the Socket constructor specifies which protocols are supported by the socket. The Socket.Connect method accepts an EndPoint object.

Next I construct my header information:

string header = 
  "POST /MyWCFMathService/Service/MathService HTTP/1.1\r\n";
header += "Content-Type: application/soap+xml; charset=utf-8\r\n";
header += "SOAPAction: 'https://tempuri.org/IMathService/Sum'\r\n";
header += "Host: localhost:8000\r\n";
header += "Content-Length: " + soapMessage.Length + "\r\n";
header += "Expect: 100-continue\r\n";
//header += "Connection: Keep-Alive\r\n\r\n";
header += "Connection: Close\r\n\r\n";

Just as with the SOAP message, I determined the header information by using a network traffic monitoring tool. Notice that each line ends with a \r\n (carriage return, linefeed) terminator rather than a single \n (newline) token, and the last line ends with a double \r\n. As I’ve indicated by the commented line of code above, depending on a number of factors you may need to send either a Keep-Alive or a Close argument to the Connection header entry. Similarly, the SOAPAction header is not necessary for a WSHttpBinding.

Now I can combine header and SOAP message, convert the entire message to bytes, and send the request:

string sendAsString = header + soapMessage;
byte[] sendAsBytes = Encoding.UTF8.GetBytes(sendAsString);
Console.WriteLine("Sending input to WCF service");
int numBytesSent = socket.Send(sendAsBytes, sendAsBytes.Length,
  SocketFlags.None);

The Socket.Send method has several overloads. Here I send the entire request without any special send or receive options. I do not make use of the return value, but I could have used that value to check that my entire message was sent. Now I can fetch the response from the WCF service:

byte[] receivedBufferAsBytes = new byte[512];
string receiveAsString = "";
string entireReceive = "";
int numBytesReceived = 0;

while ((numBytesReceived =
  socket.Receive(receivedBufferAsBytes, 512,
  SocketFlags.None)) > 0) {
  receiveAsString =
    Encoding.UTF8.GetString(receivedBufferAsBytes, 0,
    numBytesReceived);
  entireReceive += receiveAsString;
}

Because in most cases you cannot predict the number of bytes that will be in the response, the idea is to create a buffer and read chunks of the response until the entire response has been consumed. Here I use a buffer of size 512. As each group of 512 bytes are received, they are converted into text and appended to an aggregate result string.

With the response received, I check the response to see if it contains the current test case expected value:

Console.WriteLine("Response received");
if (entireReceive.IndexOf(expected) >= 0)
  Console.WriteLine("Test result : Pass");
else
  Console.WriteLine("Test result : **FAIL**");

The approach I use here is effective for very simple test scenarios, but you may have to add additional logic if your testing scenario is more complicated. I finish my harness by tying up loose ends:

. . . 
        } // main loop
        Console.WriteLine(
          "\n=======================================");
        Console.WriteLine("\nEnd test run");
      } // try
      catch (Exception ex)
      {
        Console.WriteLine("Fatal: " + ex.Message);
      }
    } // Main()
  } // Program
} // ns

Wrapping Up

There are many alternatives to WCF testing, but the socket-based approach to testing WCF services is extremely flexible. Because TCP and sockets are low-level constructions, they work in a variety of scenarios, in particular when you are testing in a technologically heterogeneous environment. For example, you could test a WCF service hosted on a Windows platform from a non-Windows client. Although you would have to modify the specific C# code I’ve presented here to a language supported by the client (typically C++ or Perl), the overall technique would be the same.

Additionally, a socket-based approach is quite useful for security testing (you must assume any WCF service will be subject to non-friendly socket probes) and performance testing (a socket approach is direct and can provide baseline round-trip timing data).


Dr. James McCaffrey works for Volt Information Sciences Inc., where he manages technical training for software engineers working at the Microsoft Redmond, Wash., campus. He has worked on several Microsoft products including Internet Explorer and MSN Search. Dr. McCaffrey is the author of “.NET Test Automation Recipes” (Apress, 2006) and can be reached at jammc@microsoft.com.

Thanks to the following technical expert for reviewing this article: Carlos Figueira