次の方法で共有


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.aspx

  • Anonymous
    July 08, 2008
    In my previous post , I explained how to unit test a WCF service with callbacks. Since the scenario involves

  • Anonymous
    July 11, 2008
    In the last couple of posts, I've demonstrated how to isolate implementation from WCF contract definition

  • Anonymous
    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