In my previous post, I talked about how to design a class that uses a WCF client proxy in such a way to make it testable in unit tests, without having to spin up a real service to answer the requests. The technique I illustrated uses a particular Inversion of Control (IoC) principle called Dependency Injection (DI), in which objects that a given class depends on get pushed into it from the outside rather than being created internally.
In this particular case, the external dependency is represented by a WCF client proxy targeting a particular service. This approach enables me to swap the real proxy objects used in production with test doubles while running unit tests, making it possible to assert the class’s behavior in a completely isolated and controlled environment.
Last time we left off with a MailClient
class that takes an instance of ChannelFactory
in the constructor and uses it internally every time it needs to download Email messages by following three steps:
MailService
serviceGetMessages
operation on the serviceHowever, as I pointed out before, being the ChannelFactory a concrete class, creating a test double for it isn’t very convenient. A much better approach would be having the MailClient class** interact with an interface instead. This way it would be easy to create a **fake factory object in unit tests and have the class use that instead of the real one.
After a quick look in the online MSDN Documentation I found that the ChannelFactory class indeed implements the IChannelFactory interface. “Sweet, I can use that!” I thought. But there’s a catch:
The IChannelFactory<TChannel>.CreateChannel
factory method requires the caller
to pass along an EndpointAddress
object, which contains information about where
and how to reach the service, like the URI and the Binding.
This isn’t really what I wanted, since it forced the MailClient
class to have knowledge of where the remote service is located or at the very least how to obtain that piece of information. This doesn’t really conform to the Dependency Injection principle, since these details naturally belong to the dependent object, and should therefore be handled outside the scope of the class.
The solution I came up with is to create an adapter interface to hide these details from my class. The implementation of this interface would then wrap a properly configured instance of ChannelFactory and delegate all the calls it receives to it.
Here is the definition of the adapter interface:
using System.ServiceModel;
public interface IClientChannelFactory<TChannel>
where TChannel : IClientChannel
{
void Open();
void Close();
void Abort();
TChannel CreateChannel();
}
And here is the default implementation:
using System;
using System.ServiceModel;
public class ClientChannelFactory<TChannel> : IClientChannelFactory<TChannel>
where TChannel : IClientChannel
{
private ChannelFactory<TChannel> factory;
public ClientChannelFactory(string endpointConfigurationName)
{
this.factory = new ChannelFactory<TChannel>(endpointConfigurationName);
}
public void Open()
{
this.factory.Open();
}
public void Close()
{
this.factory.Close();
}
public void Abort()
{
this.factory.Abort();
}
public TChannel CreateChannel()
{
return this.factory.CreateChannel();
}
}
As you can see by using generics we are able to create an implementation that works for different types of proxies.
Notice also that the class requires the callers to specify the name of the configuration element used to describe the service endpoint in the constructor. This way the MailClient class is completely isolated from having to know about WCF configuration details, and can instead concentrate on its core responsibility, that is to invoke operations on the service and work with the results.
Here is the final MailClient
implementation:
public class MailClient
{
private IClientChannelFactory<IMailServiceClientChannel> proxyFactory;
public MailClient(IClientChannelFactory<IMailServiceClientChannel> proxyFactory)
{
this.proxyFactory = proxyFactory;
}
public EmailMessage[] DownloadMessages(string smtpAddress)
{
// Validate the specified Email address
IMailServiceClientChannel proxy;
try
{
proxy = proxyFactory.CreateChannel();
Mailbox request = new Mailbox(smtpAddress);
EmailMessage[] response = proxy.GetMessages(request)
// Do some processing on the results
proxy.Close();
return response;
}
catch(Exception e)
{
proxy.Abort();
throw new MailClientException("Failed to download Email messages", e);
}
}
}
The only modification is in the type of the argument declared in the constructor, which is now an interface.
We are now able to test our class in isolation by creating fake objects that implement the IClientChannelFactory
and IMailServiceClientChannel
interfaces respectively, and inject them into our object under test.
In this particular example I am using Rhino Mocks as an isolation framework to create and manage test doubles, but I could as well used just about any other isolation framework out there, such as Typemock Isolator, with the same result.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
[TestClass]
public class MailClientTest
{
[TestMethod]
public void DownloadMessages_WithValidEmailAddress_ReturnsOneMessage()
{
// Fakes out the WCF proxy
var stubProxy = MockRepository.CreateStub<IMailServiceClientChannel>();
// Stubs the service operation invoked by the class under test
stubProxy
.Stub(m => m.GetMessages(Arg.Is.Anything))
.Returns(new EmailMessage[0]);
// Fakes out the WCF proxy factory
var stubProxyFactory = MockRepository.CreateStub<IClientChannelFactory<IMailServiceClientChannel>>();
// Stubs the factory method to return the mocked proxy
stubProxyFactory
.Stub(s => s.CreateChannel())
.Return(stubProxy);
var testObject = new MailClient(stubProxyFactory);
EmailMessage[] results = testObject.DownloadMessages("[email protected]");
Assert.AreEqual(0, results.Length, "The method was not supposed to return any results");
}
}
This concludes this short series of posts on how to apply Dependency Injection to classes that consume WCF services in order to easily test them in isolation with unit tests. I hope this helps.
/Enrico
Hi, I'm Enrico Campidoglio. I'm a freelance programmer, trainer and mentor focusing on helping teams develop software better. I write this blog because I love sharing stories about the things I know. You can read more about me here, if you like.