views:

1311

answers:

2

I'm just learning the Model / View / ViewModel pattern and it's variations (DataModel / View / ViewModel, or Model / View / Presenter).

What I wonder is: if I use this pattern with a WCF service, is the service the Model (DataModel), or do I need a separate Model to encapsulate the WCF service layer??

When I use WCF as DataModel my ViewModel is not testable without mocking the whole WCF service, since the calls to WCF need to manage the connection. The calls in this ViewModel look like this:

List<Sam.Alyza.WcfInterface.Website> rc = null;
Service<Sam.Alyza.WcfInterface.IServiceWebsites>.Use(alyzaSvc =>
{
  rc = new List<Sam.Alyza.WcfInterface.Website>(alyzaSvc.GetSites());
});

To get my ViewModel testable I tried adding a separate DataModel to abstract WCF connection away. After this the ViewModel was testable, the calls looked like this:

List<Sam.Alyza.WcfInterface.Website> rc = new List<Sam.Alyza.WcfInterface.Website>(_datamodel.GetSites());

Problem: most of the code that would need to be tested now had moved into the DataModel, which, again, needed WCF to be tested. What remained in the ViewModel was a thin shell, which can be tested. But since the main code moved into the DataModel, so testing the ViewModel was quite useless.

So to me it seems adding a separate DataModel layer to a View / ViewModel application using WCF does add a lot of work, but the testability does not get any better.

+3  A: 

Woot, after two days of wacking away at this problem I found a solution I can live with:

As seen in the code example above, I use this helper class to manage my WCF connection (because of proper handling of Close vs Abort):

public delegate void UseServiceDelegate<T>(T proxy);

public static class Service<T>
{
  public static ChannelFactory<T> _channelFactory;

  public static void Use(UseServiceDelegate<T> codeBlock)
  {
    if (_channelFactory == null)
      _channelFactory = new ChannelFactory<T>("AlyzaServiceEndpoint");
    IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
    bool success = false;
    try
    {
      codeBlock((T)proxy);
      proxy.Close();
      success = true;
    }
    finally
    {
      if (!success)
      {
        proxy.Abort();
      }
    }
  }
}

As seen in my question, this is the way this class is used in my ViewModel:

Service<Sam.Alyza.WcfInterface.IServiceWebsites>.Use(alyzaSvc =>
{
  rc = new List<Sam.Alyza.WcfInterface.Website>(alyzaSvc.GetSites());
});

To mock the WCF interface I would need to create and host a mock WCF service, change all connection strings. A lot of work just to add a few tests.

I found an easier way: It is simple to create a mock service implementing the interface:

public class MockWebsiteService : WcfInterface.IServiceWebsites
{
  internal List<Sam.Alyza.WcfInterface.Website> _websites = new List<Sam.Alyza.WcfInterface.Website>();
  internal int _GetSitesCallCount;

  IEnumerable<Sam.Alyza.WcfInterface.Website> Sam.Alyza.WcfInterface.IServiceWebsites.GetSites()
  {
    _GetSitesCallCount++;
    return _websites;
  }
}

Only problem is: how do I make the ViewModel call this mock-class instead of the service?
A solution: Service.Use() does manage the connection. By adding functionality to override connection management I'm able to sneak my own WCF mock object into Service.Use().
For this I need a way to make Service.Use() call something other than WCF (look at the #DEBUG sections):

public static class Service<T>
{
#if DEBUG
  public static T DebugOverride = default(T);
#endif
  public static ChannelFactory<T> _channelFactory;

  public static void Use(UseServiceDelegate<T> codeBlock)
  {
#if DEBUG
    if (!Object.Equals(DebugOverride, default(T)))
    {
      codeBlock(DebugOverride);
      return;
    }
#endif
    if (_channelFactory == null)
      _channelFactory = new ChannelFactory<T>("AlyzaServiceEndpoint");
    IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
    bool success = false;
    try
    {
      codeBlock((T)proxy);
      proxy.Close();
      success = true;
    }
    finally
    {
      if (!success)
      {
        proxy.Abort();
      }
    }
  }
}

By adding this testing hook into Service, I'm able to sneak in any object implementing T in my tests:

MockWebsiteService mockmodel = new MockWebsiteService();
Service<WcfInterface.IServiceWebsites>.DebugOverride = mockmodel;
// run my tests here

For me this is a very good way to mock a WCF service!

PS: I'm aware that due to the #if DEBUG the tests will not compile in release. Just kick them out if you care.

Sam
+1  A: 

We use Dependency Injection to tackle this problem, injecting the service client (or perhaps a factory for service clients) into the ViewModel. Something like this:

interface IClientFactory
{
    TClient CreateClient<TClient>();
}

class ClientFactory : IClientFactory
{
    TClient CreateClient<TClient>() 
    {
       var channelFactory = new ChannelFactory<TClient>("AlyzaServiceEndpoint");
       var proxy = (TClient)channelFactory.CreateChannel();
       return proxy;
    }
}

public ViewModel 
{
    public ViewModel(IClientFactory clientFactory)
    {
       _clientFactory = clientFactory;
    }

    private void DoWcfStuff()
    {
        using (var proxy = _clientFactory.CreateClient<IClientChannel>())
        {
           var result = proxy.GetThings();
        }
    }
}

public ViewModelTests
{
    public void Setup()
    {
       _mockFactory = new MockClientFactory();
       _viewModel = new ViewModel(_mockFactory);
    }

    [Test]
    public void Test() 
    {
       var testResult = new Result();
       var mockClient = _mockFactory.CreateClient<IClientChannel>();

       mockClient.SetResultForGetThings(testResult);

       // put the viewmodel through its paces.
    }

    private class MockClientFactory : IClientFactory
    {
        MockClient _mockClient;

        public MockClientFactory()
        {
          _mockClient = new MockClient();
        }

        public TClient CreateClient<TClient>()
        {
           if (typeof(TClient) == typeof(IClientChannel))
           {
              return _mockClient;
           }
        }
    }

    private class MockClient : IClientChannel
    {
        void SetupGetThingsResult(Result result)
        {
           _result = result;
        }

        Result GetThings() 
        {
           return _result;
        }
    }
}

I've shown an example using hand-code mocks. Usually I would use a Mocking framework like Moq.

Samuel Jack