Wednesday, April 7, 2010

Unit testing, how do I unit test WebService client X?

Another scenario I commonly encounter is the client side component to a WebService-based design.

For example,

// a client side view, displaying 
// server side profile data
public partial class ProfileView : UserControl
{
    private readonly Session _session = null;

    public ProfileViewModel ViewModel 
    {
        get { return (ProfileViewModel)(DataContext); }
        set { DataContext = value; } 
    }

    public ProfileView ()
    {
        InitializeComponent ();
    }

    // command implementation, fetches and assigns server side
    // data to client side data model
    public void Command_RefreshProfile ()
    {
        // embedded transmission codes! tsk tsk tsk
        // 
        // 1. get profile service
        ProfileWebService service = new ProfileWebService ();
        // 2. get profile
        Profile profile = service.GetProfile (_session);
        // 3. map to client side model
        ViewModel = new ProfileViewModel (profile);
    }
}

which is typical of a thick WPF client. Now, first thing to note is that the question
how do I unit test WebService client X?
is a bit of a misnomer. We are not actually interested in unit testing our WebService client, or in this case an instance of ProfileWebService. As with our WebService example what we really want to do is test our business logic and only our business logic.

Similarly then, the solution to this problem is to isolate our business from our remote invocation,

// a formal contract, defining all public operations 
// available for profile service
public class IProfileService
{
    Profile GetProfile (Session session);
}

public partial class ProfileView : UserControl
{
    private readonly Session _session = null;
    private readonly IProfileService _service = null;

    public ProfileViewModel ViewModel { get; set; }

    // we now pass in a reference to an implementation of our
    // profile service contract
    public ProfileView (IProfileService service)
    {
        InitializeComponent ();

        _service = service;
    }

    public void Command_RefreshProfile ()
    {
        // no embedded transmission codes! ah, nice clean code
        Profile profile = _service.GetProfile (_session);
        ViewModel = new ProfileViewModel (profile);
    }
}

When it comes to implementing IProfileService, we have one of two choices. If we are really lazy, and have access to the WebReference source,

// auto-generated web reference. except for these
// comments. and IProfileService definition below.
public partial class ProfileWebService : 
    System.Web.Services.Protocols.SoapHttpClientProtocol, 
    IProfileService
{
    // ...
}    

This has the advantage of no additional overhead or extraneous source files. The down-side is that maintenance is at a premium if the WebReference ever changes - which happens quite often in development. We must always remember to locate and modify the auto-generated classes with this interface definition!

Now, an alternative may be to automate the modification process, however that sounds like a lot of work to me. I would rather go the other route, and again, abstract the implementations a little.

Consider then,

// a thin wrapper for our auto-generated classes.
// has advantage of referencing most current web 
// reference when updated, without breaking existing 
// consumers
public class ProfileServiceProxy : IProfileService
{
    #region IProfileService Members

    // very simple pass through to actual auto-generated
    // implementation
    public Profile GetProfile (Session session)
    {
        // it is very important to keep this bit clean,
        // !!! NO BUSINESS LOGIC !!!
        ProfileWebService service = new ProfileWebService ();
        return service.GetProfile (session);
    }

    #endregion
}

Whichever route you go with, you have successfully isolated business from our remote service. This has the added advantage of being able to swap out ProfileServiceProxy with any implementation we like, say a WCF client, or a local instance of the actual service!

Our unit tests also benefit,

[TestMethod] 
public void Test_Command_RefreshProfile () 
{ 
    Session session = new Session ();

    IProfileService mockService = null;
    // instantiate mock with expectations 
    ProfileView view = new ProfileView (mockService); 
    view.Command_RefreshProfile (); 
    // verify results 
}