In my previous post about unit testing WCF services, I hinted at the need to perform integration testing of services as well. As Jimmy writes, you should still place your logic involving OperationContext in the Service Interface Layer (SIL): In many cases, you need to know something about the context of the operation, such as authentication and authorization, and the SIL is the correct place to place this logic.

Let's, for a moment, consider the need to perform authorization. You could inspect OperationContext.Current directly in each of your operation implementations, but that would be mixing concerns (business logic implemented in the operation mixed together with authorization). The correct way would be to provide a class deriving from ServiceAuthorizationManager, and configure the service to use this class for authorization. This would allow you to keep unit testing your operation implementations, but obviously, you also need to test the authorization manager itself, and it turns out that integration testing is the easiest way to accomplish this task.

To implement an authorization manager, you must override ServiceAuthorizationManager.CheckAccessCore or CheckAccess, which both take an instance of OperationContext as a parameter. If you could initialize a fully populated instance of OperationContext from a test, it would still be possible to unit test a custom authorization manager, but that turns out to be rather complex (OperationContext is a sealed class, so you can't derive a stub from it; on the other hand, its sole constructor takes an instance of IContextChannel, so you could theoretically create a stub of IContextChannel, but that looks like more work than the alternative). For this reason, integration testing is the most efficient approach to testing a custom authorization manager, and indeed all code involving operation context in general (such as message interceptors, etc.).

In the rest of this post, I will show you how easy it is to create a basic integration test of a WCF service. For simplicity's sake, I will start with a basic scenario where I only test the service operation itself. In future posts, I will address more advanced scenarios involving authentication and authorization.

As usual, the example service is stupidly simple:

public class MyService : IMyService
{
    #region IMyService Members
 
    public string DoStuff(string msg)
    {
        return msg;
    }
 
    #endregion
}

The integration tests should follow my principles for integration testing, which in this case are pretty easy to accomplish because WCF allows you to host a service in any managed process. Instead of having to set up a service by deploying it to, say, IIS and configuring the metabase, you can just host it directly in the test project.

Typically, I prefer setting up and starting the service only once per test suite, as starting a service takes a few seconds. As long as the service is stateless, starting the service once and having it handle all the requests from the individual test cases doesn't violate the principle of test case independence, and it saves a lot of time:

[TestClass]
public class MyServiceTest
{
    private static ServiceHost myServiceHost_;
 
    [ClassInitialize]
    public static void InitializeClass(TestContext ctx)
    {
        MyServiceTest.myServiceHost_ =
            new ServiceHost(typeof(MyService));
        MyServiceTest.myServiceHost_.Open();
    }
 
    [ClassCleanup]
    public static void CleanupClass()
    {
        MyServiceTest.myServiceHost_.Close();
    }
}

In this case, I'm using the ClassInitialize and ClassCleanup attributes to start and stop the service corresponding to the lifetime of the test class. I could also have used AssemblyInitialize and AssemblyCleanup to host the same service instance for the entirety of the test library's lifetime, but with services, I could imagine that one would sometimes prefer to be able to test the service with different deployment configurations, and by tying a service instance's lifetime to a test class, one could create different test classes that each host their own service intance.

In the example shown above, the service is hosted using configuration from the application configuration file, so an app.config file containing the necessary configuration data must be added to the test project. Even so, at this point all we have is a running service, but no client.

There are several ways to create a proxy for the service. If you are using contract-first, you can use svcutil.exe to generate a proxy class from your wsdl file. On the other hand, if you rather prefer using the Add Service Reference feature of Visual Studio, you must first host the service, but how do you do that when the service is hosted in the same project where you want to add the proxy?

The first thing to know in this regard is that if you start up a project without debugging, you can still edit the project. Since this is a test project, you need to execute the test list (without debugging). At this point, you probably haven't written any tests yet, but you need at least one (empty) test case to be able to run the test suite. Even so, you will probably not be able to add a service reference using the Visual Studio UI in the very short lifetime of the service instance. A simple hack to solve this problem is to temporarily insert a sleep statement after the service has started:

[ClassInitialize]
public static void InitializeClass(TestContext ctx)
{
    MyServiceTest.myServiceHost_ =
        new ServiceHost(typeof(MyService));
    MyServiceTest.myServiceHost_.Open();
 
    Thread.Sleep(TimeSpan.FromMinutes(1));
}

This gives you a minute in which you can add the service reference using the Visual Studio UI. When you have done this, remove the Thread.Sleep statement from InitializeClass again. You can now write a test case using the newly created proxy class:

[TestMethod]
public void InvokeServiceOperation()
{
    using (MyServiceClient proxy = new MyServiceClient())
    {
        string response = proxy.DoStuff("Ploeh");
 
        Assert.AreEqual<string>("Ploeh", response);
    }
}

In a future post, I will describe a more advanced WCF integration testing scenario.