This documentation is archived and is not being maintained.

Unit Testing: Constructing the Service and Creating the Test

Visual Studio 2010

Applies to: Windows Communication Foundation

Published: June 2011

Author: Alex Culp

Services require two constructors. The first constructor is the default constructor. This constructor uses dependency injection to resolve its dependencies. The second constructor takes all of the dependencies as parameters. To keep the code clean, the first, parameterless constructor, should call the second constructor. If you use Unity with the custom ServiceHostFactory approach to instantiate the service, the default constructor is unnecessary. The following code shows how to construct a service.

public class HRService : IHRService
{
    /// <summary>
    /// Reference to the HR repository
    /// </summary>
    private readonly IHRRepository _repository;
 
    /// <summary>
    /// Static constructor to new up any dependencies that aren't setup by unit tests or configuration.
    /// </summary>
    static HRService()
    {
        DependencyFactory.Container.RegisterInstance(typeof(IHRRepository), string.Empty, new HRRepository(), new ContainerControlledLifetimeManager());
    }
 
    /// <summary>
    /// Default constructor which gets its dependencies from DI
    /// </summary>
    public HRService():this(DependencyFactory.Resolve<IHRRepository>())
    {
 
    }
 
    /// <summary>
    /// Contructor that takes in depenencies to allow for unit testing.
    /// </summary>
    /// <param name="repository"></param>
    public HRService(IHRRepository repository)
    {
        _repository = repository;
    }
 
    /// <summary>
    /// Gets the name of the person, if it is Alex then approve the document otherwise decline it.
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public ApproveDocumentResponse ApproveDocument(ApproveDocumentRequest request)
    {
        string approver = _repository.GetApprover(request.DocumentId);
        if (approver == "Alex")
        {
            return new ApproveDocumentResponse { Approved = true };
        }
        else
        {
            return new ApproveDocumentResponse { Approved = false };
        }
    }
}

After you create a service whose dependencies are set up for dependency injection, you can test the service. The following code shows a simple approach that does not use a mock framework to simulate the dependency interfaces.

/// <summary>
/// Mock version of the repository that can be used in testing
/// </summary>
public class MockHrRepository : IHRRepository
{
    string _name;
 
    public MockHrRepository(string name)
    {
        _name = name;
    }
 
    public string GetApprover(int documentId)
    {
        return _name;
    }
}

You can use this implementation of the repository in unit tests. The following code is an example.

Sample Service Unit Test without Mocking Framework

[TestMethod]
public void HRService_ApproveDocument_Approved_When_Repository_Returns_Alex_NoMock()
{
    //assemble
    var mockRepository = new MockHrRepository("Alex");
    var target = new HRService(mockRepository);
 
    //act
    var actual = target.ApproveDocument(new Contracts.Messages.ApproveDocumentRequest { DocumentId = 0 });
 
    //assert
    Assert.IsFalse(actual.Approved);
}

While this approach works, it requires quite a bit of code to create a new class for each interface. In addition, this approach does not verify any behaviors, such as how many times the repository method is called. Fortunately, there is a better way. There are several frameworks that make it much easier to create mock implementations of interfaces, and to verify behavior. Two popular frameworks are MoQ and Rhino Mocks.

It is more difficult to unit test a workflow than to unit test an imperative service. This is because you cannot directly invoke the workflow, and because it is more difficult to inject dependencies. The following example is based on Ron Jacobs's blog "How to Unit Test a WorkflowService." The example extends his approach to include mock objects and dependency injection, which were demonstrated in this article's previous code samples.

Example

It is important to understand the steps that are required to test a workflow. Consider, for example, a simple workflow that takes a document ID as an input. If the document is an HR document, it is approved automatically. The workflow calls an imperative WCF Service to perform the approval. If the document is from any other department, it is rejected. The workflow connects to a repository to discover a particular document ID's department. The following figure illustrates the workflow process.

Referenced Image

Setting up Dependencies

To use mock objects with the workflow service, you must set up the dependencies so that they can be replaced by an Inversion of Control (IoC) container. This example uses Unity, although any IoC container works.

Just as in imperative services, dependencies must be wrapped by an interface so that they can be mocked. To convert dependencies into a concrete implementation of a class requires a wrapper to the IoC container. You can use either the sample wrapper that is included in this article, or create your own.

Access the Unity wrapper class from the workflow. The following figure illustrates the workflow's variables. To access the variable table, click on the workflow design surface and then click on the Variables button near the bottom of the designer.

Referenced Image

For the workflow service, you can use the web.config file to configure Unity. The following code is an example.

<configSections>
  <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration, Version=2.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</configSections>
<unity>
  <containers>
    <container>
      <types>
          <type type="ApprovalService.Repository.IApprovalRespository,ApprovalService"
          mapTo="ApprovalService.Repository.ApprovalRepository,ApprovalService" />
      </types>
    </container>
  </containers>
</unity>

However, the unit tests require that the dependencies be set up by using a mock framework. The following example uses MoQ.

var mockRepository = new Mock<IApprovalRespository>();
mockRepository.Setup(m => m.GetDocumentDepartment(It.IsAny<int>())).Returns("HR");

//Register the Mock repository
DependencyFactory.Container.RegisterInstance(
    typeof(IApprovalRespository),
    string.Empty, mockRepository.Object,
    new ExternallyControlledLifetimeManager());

Workflows that Call a Service

To create a mock version of a dependent service, you must use a custom activity to call services from within the workflow. This is because the built-in activity that calls a service does not support dependency injection. The following code is for a custom activity that calls a service that takes a single request parameter, and that returns a response.

/// <summary>
/// Calls a service method on a service that takes TInput input and returns a response of TOutput.
/// </summary>
/// <typeparam name="TService">Service interface to communicate with</typeparam>
/// <typeparam name="TInput">Type of input parameter</typeparam>
/// <typeparam name="TOutput">Type of output parameter</typeparam>
public sealed class CallServiceWithResponseActivity<TService, TInput, TOutput> : CodeActivity where TService : class
{
    public InArgument<string> EndpointConfigurationName { get; set; }
    public InArgument<string> Operation { get; set; }
    public InArgument<TInput> Input { get; set; }
    public OutArgument<TOutput> Output { get; set; }
 
    /// <summary>
    /// Calls the service with the <typeparamref name="TInput"/> Input property and sets the 
    /// <typeparamref name="TOutput"/> Output property with the result.
    /// </summary>
    protected override void Execute(CodeActivityContext context)
    {
        TService service = DependencyFactory.Resolve<TService>();
 
        if (service == null)
        {
            //service is not registered with IoC, so use WCF to resolve the interface
            var channelFactory = new ChannelFactory<TService>(this.EndpointConfigurationName.Get(context));
            service = channelFactory.CreateChannel();
        }
 
        //grab communication object if available so we can clean up later.
        var serviceCommunicationObject = service as ICommunicationObject;
 
        try
        {
 
            TOutput response = (TOutput)typeof(TService).InvokeMember(
                    this.Operation.Get(context),
                    BindingFlags.InvokeMethod,
                    null,
                    service,
                    new object[] { this.Input.Get(context) });
 
            Output.Set(context, response);
 
        }
        catch
        {
            //Make sure we abort the service instead of closing it
            if (serviceCommunicationObject != null)
            {
                serviceCommunicationObject.Abort();
            }
            throw;
        }
 
        if (serviceCommunicationObject != null)
        {
            //close the communication channel on success
            serviceCommunicationObject.Close();
        }
    }
}

If the workflow uses a custom activity to support dependency injection, you can create and register mock versions of the dependent service. The following code shows how to do this.

//create mock version of the hr service
var mockHRService = new Mock<IHRService>();
mockHRService.Setup(m => m.ApproveDocument(It.IsAny<Contracts.Messages.ApproveDocumentRequest>())).Returns(new Contracts.Messages.ApproveDocumentResponse { Approved = true });

//Register the Mock HR service
DependencyFactory.Container.RegisterInstance(
    typeof(IHRService),
    string.Empty, mockHRService.Object,
    new ExternallyControlledLifetimeManager());

Hosting the Workflow

Unfortunately, you cannot simply create a new instance of the workflow and then call operations on it. To consume the workflow, you must host it. Although it is possible to use Cassini, Internet Information Services (IIS), or Express IIS, they all introduce external dependencies. For example, Cassini must be running. To use IIS, you must have it on your machine, and run Visual Studio as an administrator. Instead, the best option is to self-host the workflow from the unit test process. The example workflow service in this article requires this because it assumes that the workflow and the test are in the same process. Fortunately, the WF Codeplex makes this is a fairly simple process.

To begin, declare the following two properties in your unit test class.

private readonly Binding _binding = new NetNamedPipeBinding();
private readonly EndpointAddress _serviceAddress = new EndpointAddress("net.pipe://localhost/UnitTest");

A named pipes binding is probably the best binding to use because it avoids port conflicts both on the workstation, and the build server.

In the unit test, include the following code in order to self-host the workflow.

using (var testHost = WorkflowServiceTestHost.Open("Approval.xamlx", _serviceAddress.Uri))
{
    //Call the service
    using (var target = new ApprovalServiceClient(_binding, _serviceAddress))
    {
        target.Open();
        actual = target.ApproveDocument(approveDocumentMessage);
    }
}

Calling the Service

Unfortunately, with workflows, there is no way to define service contracts and operation contracts in the traditional, imperative way (that is, create an interface and decorate it with the ServiceContractAttribute). To unit test a workflow service, you must use a proxy class. You can create a proxy either by using Svcutil or by adding a service reference to your workflow. To reduce the risk of missing important changes to the proxy, reference the assemblies that contain the data contracts. If you use Svcutil, use the /reference option. For more information about Svcutil, see ServiceModel Metadata Utility Tool (Svcutil.exe).

The following paragraphs describe some of the recommended best practices for creating unit tests.

Mock Dependencies

In order to isolate the method that you are testing, you should use mock objects for all its dependencies. If a unit test project has an app.config file, you are doing something wrong, because you have not successfully abstracted away all dependencies.

One Assert Per Test

In general, it is considered a best practice to have only one assert statement for each test. This restriction is a great help in isolating the exact cause of a problem when a test fails. However, this rule may also result in a large number of tests. For example, if you test a function that maps two objects with 20 properties each, you could have 20 tests, each with a single assert that a single property is mapped correctly. In this case, 20 tests is probably appropriate. If there were multiple asserts for each test, and multiple properties were not mapped correctly, you would see only one broken test, and that would be for the first field only.

Previous article: Unit Testing: Introduction

Continue on to the next article: Integration Testing

Show: