Freigeben über


Unit Testing Duplex WCF Clients

In my previous post, I explained how to unit test a WCF service with callbacks. Since the scenario involves duplex communication, the service is also a client, and vice versa, so it's only reasonable to look at the 'client' as well, to examine how to best approach unit testing such a beast.

Since both ends both act as sender and receiver, they are each both client and service. None the less, there's still a clear distinction, since the client is the party that initiates and terminates a session, whereas the service is always ready and available for whatever client that may come along.

Ensuring proper isolation and testability is most difficult for the service, which was why I decided to tackle that issue first. Making the client testable is much easier, and I'll describe how to do that here by continuing the example from my former post.

When writing the client, the only thing I really need to abstract away is the creation of the WCF proxy and its callback instance.

When you look at the standard WCF documentation for duplex clients, a duplex proxy can be created by a DuplexChannelFactory<TChannel> like this:

 DuplexChannelFactory<IStuffService> cf = 
     new DuplexChannelFactory<IStuffService>(callbackService,
         binding, remoteAddress);

You can then invoke CreateChannel on the cf instance to get an instance of an IStuffService proxy.

However, now that you have decided to implement your client without any reference to WCF, the use of DuplexChannelFactory<TChannel> and its ilk from within that library is not permitted (and should indeed be impossible, since you don't have a reference to System.ServiceModel).

What the client really needs is something that can create an instance of an IStuffService proxy, while itself receiving an instance of IStuffCallbackService (which is the type of the callbackService parameter above). In other words, I'll need to define an abstraction that takes IStuffCallbackService as input and returns IStuffService.

In a move very similar to the approach described previously, a Func<IStuffCallbackService, IStuffService> is just what we need. Hence, a rudimentary implementation of StuffClient would be similar to this:

 public class StuffClient : IStuffCallbackService
 {
     private readonly Func<IStuffCallbackService, IStuffService> createProxy_;
  
     public StuffClient(Func<IStuffCallbackService, IStuffService> proxyCreator)
     {
         if (proxyCreator == null)
         {
             throw new ArgumentNullException("proxyCreator");
         }
  
         this.createProxy_ = proxyCreator;
     }
  
     public void DoStuff(string stuff)
     {
         IStuffService proxy = this.createProxy_(this);
         proxy.DoStuff(stuff);
  
         // Remeber to safely dispose of proxy
     }
  
     // Other members, including IStuffCallbackService Members...
 }

The abstract proxy creator is required by way of Constructor Injection, and is used in the DoStuff method to create an IStuffService proxy instance, which may or may not be a WCF proxy.

If the proxy instance is a real WCF proxy, it will implement IDisposable and should be properly closed. There are several strategies for dealing with this situation, but to keep the example as simple as possible, I've chosen to omit this particular aspect, and leave it as an exercise for the interested reader :)

Although I have yet to show the actual implementation, note that StuffClient implements IStuffCallbackService, so it can return itself to the createProxy_ delegate instance as the callback service. Obviously, if you use the same object as both sender and receiver, you need to do so in a thread-safe manner, but since I can, in this particular context, bask in the luxury of writing sample code, I choose to ignore thread-safety completely :)

 #region IStuffCallbackService Members
  
 public void StuffWasDone(string result)
 {
     this.stuffResults_.Add(result);
 }
  
 #endregion

where stuffResults_ is a List<string>.

Unit testing StuffClient is now fairly simple. We'll need an implementation of IStuffService to act as a Test Double, so I've created a simple SpyStuffService class that records all the stuff strings in a List<string> called Stuffs.

First of all, we need to ensure that the proxyCreator delegate instance receives a proper instance of IStuffCallbackService as input:

 [TestMethod]
 public void DoStuffWillCreateProxyByPassingNonNullCallback()
 {
     // Fixture setup
     string anonymousStuff = "ploeh";
     SpyStuffService dummy = new SpyStuffService();
     StuffClient sut = new StuffClient(callbackService =>
         {
             Assert.IsNotNull(callbackService,
                 "Callback service");
             return dummy;
         });
     // Exercise system
     sut.DoStuff(anonymousStuff);
     // Verify outcome (done by inline mock);
     // Teardown
 }

A bit unusually, test verification happens inside the delegate defined by the lambda expression passed to the constructor of StuffClient - a technique I call an Inline Mock. Since C# is strongly typed, I can be assured that if callbackService is not null, it will be an instance of IStuffCallbackService.

Next, we may want to verify that when DoStuff is invoked, data will be transferred to the service:

 [TestMethod]
 public void DoStuffWillSendStuffToService()
 {
     // Fixture setup
     string expectedStuff = "ploeh";
     SpyStuffService spy = new SpyStuffService();
     StuffClient sut =
         new StuffClient(callbackService => spy);
     // Exercise system
     sut.DoStuff(expectedStuff);
     // Verify outcome
     Assert.AreEqual<string>(expectedStuff, 
         spy.Stuffs.First(), "Stuff");
     // Teardown
 }

The Spy is passed to the SUT in the constructor, and later inspected in the verification phase. Pretty classic four-phase unit test setup...

Finally, we may want to test the SUT's implementation of IStuffCallbackService:

 [TestMethod]
 public void StuffWasDoneWillAddResultToStuffResultList()
 {
     // Fixture setup
     string expectedStuff = "ploeh";
     SpyStuffService dummy = new SpyStuffService();
     StuffClient sut = 
         new StuffClient(callbackService => dummy);
     // Exercise system
     sut.StuffWasDone(expectedStuff);
     // Verify outcome
     Assert.AreEqual<string>(expectedStuff,
         sut.StuffResults.First(), "Stuff result");
     // Teardown
 }

At this point, the SpyStuffService instance is not used by the SUT, but we need it for the Func<IStuffCallbackService, IStuffService> instance used in the StuffClient constructor, so it's a classic example of a Test Dummy.

The test invokes the StuffWasDone method and subsequently verifies that the string passed to it becomes available in the StuffResults list, so again, it's really just a standard four-phase unit test.

If you've managed to read this far, I owe it to you to show how to set up StuffClient with a real WCF proxy. Remember, this must be done in another library that can reference System.ServiceModel, since the library where StuffClient is implemented must not reference WCF. The most natural place to do this is in a Humble Object, such as a top-level executable, as described here.

 StuffClient client = new StuffClient(callbackService =>
     {
         DuplexChannelFactory<IStuffService> cf = 
             new DuplexChannelFactory<IStuffService>(callbackService,
                 binding, remoteAddress);
         return cf.CreateChannel();
     });

Note that this is the place where WCF reappears after its long absence. The StuffClient implementation has no dependency on WCF, and testability is ensured.

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

Comments

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