A couple of weeks ago I pinged Paulo for some help about a WCF service I was writing – he solved the problem I was having and replied that he liked the unit testing pattern I was using for starting the WCF service and couldn't believe he didn't think of it. So I thought I'd blog it, just in case anyone else was interested.
Basically you create a internal ServiceHost wrapper class, which creates a new instance of ServiceHost based on the type of the WCF service you're trying to test; this wrapper class has two static methods exposed: StartService() and StopService() which, starts and stops the ServiceHost instance. If you are using the Visual Studio Unit Testing framework, in your unit test class you call StartService()in the method marked with the [ClassInitialize] attribute and StopService()in the method marked by the [ClassCleanUp] attribute. If you are using MBUnit or NUnit you would use [TestFixtureSetUp] instead of [ClassInitialize] and [TestFixtureTearDown] instead of [ClassCleanUp].
namespace Conchango.WebTwo.Services.Tests
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.ServiceModel;
using Conchango.Services.Resourcing;
using Conchango.Services.Contracts;
using Conchango.WebTwo.CommonTypes.Resourcing;
[TestClass]
public class ResourcingServiceTests
{
/// <summary>
/// Start the Resourcing WCF Service so all subsequent
/// calls made by the unit test use this connection
/// </summary>
/// <param name="testContext">Current Test Context</param>
[ClassInitialize()]
public static void ClassInitialize(TestContext testContext)
{
ResourcingServiceHost.StartService();
}
/// <summary>
/// Shut down the WCF service once all tests have been run
/// </summary>
[ClassCleanup()]
public static void MyClassCleanup()
{
ResourcingServiceHost.StopService();
}
/// <summary>
/// Search for people called "Howard" at Conchango - expect 2 matches
/// </summary>
[TestMethod]
public void PeopleCalledHowardTest()
{
using (ChannelFactory<IResourcingContract> factory = new ChannelFactory<IResourcingContract>(string.Empty))
{
IResourcingContract client = factory.CreateChannel();
People people = client.GetPeopleByPartialName("Howard");
Assert.AreEqual(2, people.Count);
}
}
}
internal class ResourcingServiceHost
{
internal static ServiceHost Instance = null;
internal static void StartService()
{
Instance = new ServiceHost(typeof(ResourcingService));
Instance.Open();
}
internal static void StopService()
{
if (Instance.State != CommunicationState.Closed)
{
Instance.Close();
}
}
}
}
One other point of note; on the line:
using (ChannelFactory<IResourcingContract> factory = new ChannelFactory<IResourcingContract>(string.Empty))
the string.Empty is required in the ChannelFactory contructor otherwise the following Exception is thrown: "System.InvalidOperationException: The Address property on ChannelFactory.Endpoint was null. The ChannelFactory's Endpoint must have a valid Address specified."