One of my readers recently asked me about unit testing WCF services when they have callbacks. Given that I strongly believe that you should attempt to implement your services without referencing WCF at all, but duplex WCF services require you to get the callback instance from OperationContext.Current, how can these two forces be reconciled?

Fortunately, it's really not that hard. All you have to do is to replace the call to OperationContext.GetCallbackChannel<T> with something abstract. On .NET 3.5, the easiest abstraction is Func<TResult>, which has the same signature, but if you are on .NET 3.0, you can always define a similar delegate type of your own.

Let's say that your contracts look like this:

[ServiceContract(CallbackContract = typeof(IStuffCallbackService))]
public interface IStuffService
{
    [OperationContract]
    void DoStuff(string stuff);
}
 
public interface IStuffCallbackService
{
    [OperationContract]
    void StuffWasDone(string result);
}

Since it would ruin testability of the IStuffService implementation if it was to use OperationContext.GetCallbackChannel<T> to create a new instance of IStuffCallbackService, it needs an instance of Func<IStuffCallbackService> instead. As I favor Constructor Injection, the complete implementation looks like this:

public class StuffService : IStuffService
{
    private readonly Func<IStuffCallbackService> createCallbackChannel_;
 
    public StuffService(Func<IStuffCallbackService> callbackCreator)
    {
        if (callbackCreator == null)
        {
            throw new ArgumentNullException("callbackCreator");
        }
        this.createCallbackChannel_ = callbackCreator;
    }
 
    #region IStuffService Members
 
    public void DoStuff(string stuff)
    {
        // Implementation goes here...
        string stuffResult =
            new string(stuff.ToCharArray().Reverse().ToArray());
 
        this.createCallbackChannel_().StuffWasDone(stuffResult);
    }
 
    #endregion
}

Such an implementation is imminently testable, as this test demonstrates:

[TestMethod]
public void DoStuffWillInvokeCallbackService()
{
    // Fixture setup
    string anonymousStuff = "ploeh";
    string expectedResult = 
        new string(anonymousStuff.ToCharArray().Reverse().ToArray());
 
    SpyStuffCallbackService spy = new SpyStuffCallbackService();
 
    StuffService sut = new StuffService(() => spy);
    // Exercise system
    sut.DoStuff(anonymousStuff);
    // Verify outcome
    Assert.AreEqual<string>(expectedResult,
        spy.StuffResults.First(), "Callback result");
    // Teardown
}

The SpyStuffCallbackService class is a simple Test Spy that records all the callbacks in the StuffResults collection.

When you let WCF host the service, you need to tell WCF to use OperationContext.GetCallbackChannel<IStuffCallbackService> as the delegate instance to the StuffService constructor. In my previous post, I demonstrated how to do that (that's what StuffInstancingBehavior.GetInstance does).

Update (2008-07-12): I've just posted an overvview of the solution, as well as all the sample code.