Freigeben über


Testing WCF service implementations

When writing WCF implementations, or any code for that matter, testing is an important part of the process. This week, I was building tests for implemented WCF services and had difficulty aligning all the external dependencies of the tests (running hosts, configuration settings, behavior component references, etc). As a result, I set up my WCF services to be self-hosted services, run from the context of the test itself. This has several advantages, and some pitfalls. The advantages:

 

· Isolation, the test has external dependencies only to the actual service implementation.

· No manual setup running hosts to ensure they are available, the test ‘just works’.

 

The pitfall might be:

 

· Configuration settings could become out-of-sync. Because the host of the service implementation runs within the context of the test and thereby it’s AppDomain, you need to configure the service ABCs within the test configuration file. This could mean over time you’d get configuration settings for the tests no longer in sync with the actual host.

 

However, because the scope of the tests is under your control and so is it’s configuration, the described pitfall might not pose an issue.

 

In order for the test to host it’s own instance of the services to be tested I created a generic container. It’s a simple, but effective, class which can easily host different service types (all tracing and the IDisposable implementation removed for clarity):

 

/// <summary>

/// Defines a generic implementation of a runnable background host.

/// </summary>

/// <typeparam name="T">The service to run within the host.</typeparam>

public class RunnableBackgroundHost<T> : IDisposable where T : class, new()

{

internal AutoResetEvent stopEvent = new AutoResetEvent(false);

/// <summary>

/// Starts the host on a new thread.

/// </summary>

public void Run()

{

            ThreadPool.QueueUserWorkItem(

new WaitCallback(delegate(object state)

            {

                using (ServiceHost host = new ServiceHost(typeof(T)))

                {

                    host.Open();

                    stopEvent.WaitOne();

                    host.Close();

                }

}));

}

/// <summary>

/// Stops the running host.

/// </summary>

public void Stop()

{

stopEvent.Set();

}

}

Using this class, testing services become quite easy. Set up the host before starting the test:

 

[System.Diagnostics.CodeAnalysis.SuppressMessage(

"Microsoft.Performance",

"CA1812:AvoidUninstantiatedInternalClasses"), TestClass]

class HostInit

{

protected static RunnableBackgroundHost<Service1> service1;

protected static RunnableBackgroundHost<Service2> service2;

 

[AssemblyInitialize]

public static void AssemblyInitalize(TestContext context)

{

service1 = new RunnableBackgroundHost<Service1>();

service2 = new RunnableBackgroundHost<Service2>();

 

service1.Run();

service2.Run();

}

 

[AssemblyCleanup]

public static void AssemblyCleanup()

{

service1.Stop();

service2.Stop();

}

}

 

And set up your test, using a generated proxy (svcutil) and assuming the service is alive:

 

[TestClass]

public class Service1Tests : IDisposable

{

private Service1Client service1Client;

[TestInitialize]

public void TestInitialize()

{

service1Client = new Service1Client();

}

[TestMethod]

public void TestService1Method1()

{

Service1Request request = new Service1Request();

request.Id = "1";

Service1Response response = service1Client.Method1(request);

            Assert.IsNotNull(response, ErrorMessages.NullResponse);

            Assert.AreEqual<int>(

response.Field,

1,

ErrorMessages.NonMatchingId);

}

}

ErrorMessages is a simple resource file containing the messages the test would display in case of failure.

 

What the test framework does in this case:

 

· Call the static method HostInit.AssemblyInitalize marked with [AssemblyInitialize].

· For every class within the test marked with [TestClass]

o Call any instance method marked with [TestInitialize].

o Run any method marked with [TestMethod].

o ...

· Call the static method HostInit.AssemblyCleanup marked with [AssemblyCleanup].

 

Within the test configuration file, configure both the service host and the client:

 

<system.serviceModel>

<client>

<endpoint

address="https://localhost:8889/Services/Service1"

binding="basicHttpBinding"

contract="IService1" />

</client>

<services>

<service name="Service1">

<endpoint

address="https://localhost:8889/Services/Service1"

binding="basicHttpBinding"

contract="IService1" />

                  </service>

</services>

</system.serviceModel>

That’s the basic setup. You can now easily test different services by defining the tests for it in it’s own class using it’s own generated client proxy and adding the service to the HostInit class. You could test behaviors by adding them to the configuration file and writing a test for them if they can be tested from the outside.

 

Just press SHIFT+ALT+X to run the test in the debugger. No console windows popping up, your test just executes :).

Comments