Unit Testing Duplex WCF Services
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.
Comments
Anonymous
June 28, 2008
PingBack from http://blogs.msdn.com/ploeh/archive/2008/06/27/modifying-behavior-of-wcf-free-service-implementations.aspxAnonymous
July 08, 2008
In my previous post , I explained how to unit test a WCF service with callbacks. Since the scenario involvesAnonymous
July 11, 2008
In the last couple of posts, I've demonstrated how to isolate implementation from WCF contract definitionAnonymous
July 24, 2008
Hi, Mark. Your methodology of unit testing of Duplex WCF Services is very interesting for me. I understand that is very GLOBAL question, but how can I use it at WCF service with many subscribers (callback sends not only to one client, but to all subscribers)?Anonymous
August 02, 2008
Hi Alexander Thank you for your question. If you take a look at e.g. http://idunno.org/archive/2008/05/29/wcf-callbacks-a-beginners-guide.aspx for an approach to web service eventing, you'll see that it's very similar to my examples. The major difference is that instead of immediately invoking the callback, it's being saved for later. You can basically proceed with unit testing as I describe above. When first you've managed to pull WCF away from the implementation, unit testing can proceed like unit testing of any other code. When dealing with collections of multiple objects, I typically find that a good equivalence count is the number three. In your case, it would translate to that if you can unit test successfully with three subscribers, it's probably also going to work with any larger number as well (functionally, that is - keep in mind that a unit test is never a performance or stress test). HTH