What a challenge I had to try and create unit tests for workflows that is hosted by WCF services. This challenges took me an month to investigate and solve.

So, here I will explain and give code to show you how to do proper unit testing. I'm going under the assumption that you do know how to hook up a Workflow with WCF service. Otherwise just follow the code in the example project and you will quickly get it.

This post will show the basics first and later I will provide more posts of how to write unit test for different scenarios with ReceiveActivity and SendActivity.

First off, I created a simple project to illustrate the scenario to create a client object and post it to a service that has a workflow. The workflow is very basic that it will receive the Client object message and save it to a database. I will just fake the actually writing to the database.

The project structure:


Firstly we define the ServiceContract IClientService and hook it up to the workflow CreateClientWorkflow.xoml ReceiveActivity.

Next I created a custom WorkflowServiceHostFactory that I will explain later in another post. I removed the code behind for the ClientService.svc and made the following changes.
   1: <%@ ServiceHost Service="ClientWorkflowLibrary.CreateClientWorkflow, ClientWorkflowLibrary, Version=, Culture=neutral"
   2:                 Factory="CommonLibrary.WorkflowServiceHostFactory, CommonLibrary, Version=, Culture=neutral"  %>
This basically show some of the key setup points. Now for the main topic namely the Unit Test. The one unit test is straight forward that you start you service and create a service reference and use a client proxy to communicate with the service, but that is more of an integration test. I will show know the correct way to test your workflow service.
The Proper Unit Test Code
   1: [TestMethod]
   2: [Description("Run Unit Test with Administrator privlidges. (VS in Administrator mode)")]
   3: public void CreateClientWorflow_ReceiveCreateClient_ReturnClientWidthValidGuid()
   4: {
   5:     // Arrange
   6:     Client requestClient = new Client
   7:                                {
   8:                                    Id = Guid.Empty,
   9:                                    Name = "User",
  10:                                    Surname = "Nobody"
  11:                                };
  12:     Client responseClient = null;
  14:     // Setup WCF and WF
  15:     Uri baseAddress = new Uri("");
  16:     WorkflowServiceHost host = new WorkflowServiceHost(typeof(CreateClientWorkflow), baseAddress);
  18:     host.AddServiceEndpoint(typeof(IClientService), new WSHttpContextBinding(), "CreateClient");
  20:     ServiceMetadataBehavior smb = new ServiceMetadataBehavior { HttpGetEnabled = true };
  21:     host.Description.Behaviors.Add(smb);
  23:     // Add Custom Services to Workflow runtime
  24:     WorkflowRuntimeBehavior runtime = host.Description.Behaviors.Find();
  25:     runtime.WorkflowRuntime.AddService(new ClientRepository());
  27:     // To enable Workflow Tracking and Persistence
  28:     //string connectionString = "Data Source=(local);Initial Catalog=Workflow;Integrated Security=true";
  29:     //runtime.WorkflowRuntime.AddService(new SqlTrackingService(connectionString));
  30:     //runtime.WorkflowRuntime.AddService(new SqlWorkflowPersistenceService(connectionString));
  32:     // Act
  33:     try
  34:     {
  35:         host.Open();
  36:         EndpointAddress address = new EndpointAddress("");
  37:         IClientService service = ChannelFactory.CreateChannel(new WSHttpContextBinding(), address);
  39:         responseClient = service.CreateClient(requestClient);
  41:         host.Close();
  42:     }
  43:     catch (Exception e)
  44:     {
  45:         Assert.Fail(string.Format("Error: {0}", e));
  46:     }
  47:     finally
  48:     {
  49:         if (host.State != CommunicationState.Closed)
  50:         {
  51:             host.Abort();
  52:         }
  53:     }
  55:     // Assert
  56:     Assert.IsNotNull(responseClient);
  57:     Assert.AreNotEqual(Guid.Empty, responseClient.Id);
  58:     Assert.AreEqual(requestClient.Name, responseClient.Name);
  59:     Assert.AreEqual(requestClient.Surname, responseClient.Surname);
  60: }
To be able to run this code you need Administrator privileges. I follow the AAA syntax in unit testing. The Arrange part is where I setup my requestClient object and the WCF and Workflow configuration.

Firstly you setup the Uri address of where you want to host the service that will be dynamically get set as the unit test is run. Then you create the WorkflowServiceHost and assign the workflow that you want to test. Add an Service endpoint with the ServiceContact that is used in the workflow and the appropriate binding that should be used. Finally a name for the endpoint that you will call later to test.

You can add additional behaviour metadata. When you instantiate a WorkflowServiceHost then the WorkflowRuntimeBehaviour is automatically added. In the code I do a find on the host Behaviours to return the WorkflowRuntimeBehaviour instance. This instance is required to add additional custom service. For example the ClientRepository.You will see the code comment if you would like to add Tracking and Persistence to you workflow under test.

Now for the Act part. With WorkflowServiceHost setup we can open a connection to the service. I create a channel to the ServiceContract IClientService and assign the endpoint address that should be queried.
I call the relevant method on the ServiceContract and close the host connection afterwards. 
There is a catch and finally section to make sure that no exceptions was raised and that the host was properly closed.

The Assert part is at the end to separate the asserts from any communication problems. I specify multiple asserts just to show the variety of unit tests that could be written and should actually be written to different unit tests.

Hope you enjoy this post and any feedback or recommendation is welcome!


Source: ClientService 1.zip

Categories: , , , ,