WCF services provide middleware for applications but can be hard to test. Steve Love describes a way to develop a testable app.
The Windows Communication Foundation, or WCF, is part of a loosely related family of frameworks from Microsoft for developing robust and reliable systems. I say loosely because WCF and the other libraries can be used independently or in concert. The other main frameworks are WPF for presentation, and WF for workflow. WCF is for distributed and inter-process communications. Specifically, it is the middleware that applications can use to talk to each other, whether they are on the same machine, distributed over a LAN or even on the Internet.
There is a wealth of information about WCF on the MSDN [ WCF , MSDN ], and in a whole range of books [ Resnick08 ] which presents the details of writing and configuring services and client applications. From reading this documentation, one would be forgiven for thinking that the ‘W’ in WCF was ‘Web’ rather than ‘Windows’ because much of it covers setting up a web-service, and using SOAP to establish communications and discover services over HTTP. WCF is, however, the official replacement for .Net Remoting Services, which itself superceded Distributed COM. It’s my experience that WCF is actually really quite good in this application space, even if the documentation (official and otherwise) wants you, the developer, to think about web services and SOAP instead of RPC.
In this article, I want to explore some of those almost-hidden secrets of configuring and running WCF service- and client-applications. By going off the beaten path blazed by the example code that is available on MSDN, I hope to demonstrate how the resulting code can be more flexible and less intrusive in your applications.
The full source code for this is available on github [ Love ]. It’s a Microsoft Visual Studio 2010 solution in C#, but should work in VS2012. A wiki page on the github site explains the different projects in the code.
A standing start
There are two main starting points from where discussion of using WCF for applications might commence. The first is to begin with the premise that the application will be distributed, that WCF has been chosen as the facilitator for that, and so we would start with defining some service contracts and definitions. The second scenario – which is probably much more common – is to begin with the premise that there is some application already in existence, for which WCF has been chosen as the technology to turn it into a distributed application. Most of the MSDN introductions, and many of the books on WCF follow the first scenario, but I’m now going to introduce a third possibility.
I want to start with a new application that doesn’t use WCF in any way, and turn it into a distributed application.1 The main reason for this is about testing. Distributed applications that use technologies like WCF are notoriously hard to test in an automated fashion. Testing the client usually has to assume that a corresponding server is available, and the client must be configured correctly to use it. Testing the server is notionally simpler if you separate the communications code from the ‘server’ code (as it were) internally, so you can at least automate testing the code that provides the logic for your service, but it’s all rather messy. Which is probably why discussion of this kind is conspicuously absent from MSDN examples and books about WCF.
The example code for this article is a recipe book application for cocktails. It isn’t terribly sophisticated, and the outline of the design ought to be clear from Listing 1.
public interface RecipeBook : IDisposable { IEnumerable< Drink > AllDrinks { get; } IEnumerable< string > AllIngredients { get; } void Add( params Drink [] newDrinks ); IEnumerable< Drink > WithIngredients ( params string [] selected ); } public interface Drink : IEquatable< Drink > { string Name { get; } string Method { get; } IEnumerable< Ingredient > Ingredients { get; } } public enum Measurement { Fill, Measure, Drop, Tsp, } public sealed class Ingredient : IEquatable< Ingredient > { public Ingredient( string name, Measurement mmt, int qty ) { if( string.IsNullOrEmpty( name ) ) throw new ArgumentNullException( "name" ); Name = name; Amount = mmt; Qty = qty; } public string Name { get; private set; } public Measurement Amount { get; private set; } public int Qty { get; private set; } } |
Listing 1 |
One thing of note here, for the sake of brevity later, is that the
Drink
type is an interface instead of a value-type like
Ingredient
. Consider it a foresight of things to come in the design when we start considering distributed communications.
In any case, there is nothing here, or in the implementing types, to do with WCF or any kind of remoting. To make the example more interesting, let’s introduce a new class that uses the recipe book, and adds some value. Listing 2 uses the
RecipeBook
interface, and provides some simple queries over those in
RecipeBook
. Lastly listing 3 has some unit tests for the
DrinksCabinet
queries. In this test class you can see that the concrete
LocalDrink
type is used; this is a value-like implementation of the
Drink
interface mentioned previously. The
DrinksCabinet
constructor takes a
RecipeBook
reference, and the test creates a
LocalRecipeBook
which implements that interface using simple collection types to store the data.
public class DrinksCabinet { public DrinksCabinet( RecipeBook recipes ) { this.recipes = recipes; } public Drink Find( string name ) { return recipes.AllDrinks.Single ( d => d.Name == name ); } public IEnumerable< string > Ingredients { get { return recipes.AllIngredients; } } public IEnumerable< Drink > NotContaining ( params string [] selected ) { var remain = Ingredients.Except( selected ); return recipes.WithIngredients ( remain.ToArray() ); } private readonly RecipeBook recipes; } |
Listing 2 |
[ TestFixture ] public class DrinksCabinetTests { private RecipeBook recipes; [SetUp] public void Start() { recipes = new LocalRecipeBook(); } [TearDown] public void End() { recipes.Dispose(); } [Test] public void EmptyCabinetHasNoIngredients() { var cabinet = new DrinksCabinet( recipes ); var results = cabinet.Ingredients; Assert.IsFalse( results.Any() ); } [ Test ] public void CanLocateSpecificDrinkByName() { var cabinet = new DrinksCabinet( recipes ); var expected = new LocalDrink( "a", "", new[] { new Ingredient( "1", Measurement.Tsp, 1 ) } ); var error = new LocalDrink( "b", "", new[] { new Ingredient( "2", Measurement.Tsp, 1 ) } ); recipes.Add( expected, error ); var result = cabinet.Find( expected.Name ); Assert.AreEqual( expected, result ); } [ Test ] public void CanFilterDrinksOnNotSpecified() { var cabinet = new DrinksCabinet( recipes ); var expected = new LocalDrink( "a", "", new[] { new Ingredient( "1", Measurement.Tsp, 1 ) } ); var error = new LocalDrink( "b", "", new[] { new Ingredient( "2", Measurement.Tsp, 1 ) } ); recipes.Add( expected, error ); var results = cabinet.NotContaining( "2" ); Assert.AreEqual( expected, results.Single() ); } } |
Listing 3 |
Clearly, we could use any implementation of
RecipeBook
to provide to the
DrinksCabinet
object, which brings us neatly to the next section.
Pace yourself
Would it be possible to use the
RecipeBook
interface as the contract for a remote service? If it is, then the service can implement it in some way, and the client can use a proxy implementation to communicate. Well, we can’t use the interface directly, but it would certainly be possible to modify it in simple ways to operate as a WCF contract. However, let’s step back for a moment. WCF can be used to provide network communications, as well as inter-process, and should be less ‘chatty’ than a native interface. A direct port may not be at all appropriate if it would introduce the need for unnecessary communications between client and server. Adapting a native interface to reduce the communications requires an intermediate abstraction.
Additionally, WCF imposes some restrictions on the way you compose contracts. WCF requires those contracts to explicitly attribute the interfaces, methods and types used in the contract. Ideally, keeping these WCF specific warts out of the way of application code would be a Good Idea.
Listing 4 shows what a WCF contract version of the
RecipeBook
interface might look like. It’s not a direct port from
RecipeBook
, partly because I want to demonstrate adapting one interface to the other, but there are other differences that are due to it being part of the WCF subsystem. Also note the use of names such as
IngredientDto
(
Dto
indicates it’s a
DataTransferObject
, see [
Fowler
,
Fowler02
]) instead of just
Ingredient
. We could easily use the same names for the component types as in the original
RecipeBook
interface that we’re adapting, and namespaces would take care of clashes. However, the adapting code would necessarily contain lots of explicit namespace qualification to distinguish between the native and DTO versions, and it can get difficult to keep track of which side of the WCF boundary you are on.
[ ServiceContract ] public interface RecipeBookContract { [ OperationContract ] IEnumerable< DrinkDto > AllDrinks(); [ OperationContract ] IEnumerable< string > AllIngredients(); [ OperationContract ] IEnumerable< IngredientDto > IngredientsOf ( string drink ); [ OperationContract ] void Add( DrinkDto drink, IEnumerable< IngredientDto > newIngredients ); [ OperationContract ] IEnumerable< DrinkDto > WithIngredients ( IEnumerable< string > ingredients ); } |
Listing 4 |
The
AllIngredients
and
AllDrinks
properties from
RecipeBook
are methods in the contract. A
ServiceContract
is essentially a collection of
OperationContracts
, which are required to be methods (as opposed to properties). Another restriction is on the use of
params
arrays, so these have become
IEnumerable
objects instead, which is only a minor inconvenience really in any case.
Listing 5 shows the participating classes. Note that
DrinksDto
is
not
an interface here, because it’s not a
ServiceContract
, it’s a
DataContract
. One other difference to note is that a
DrinksDto
doesn’t directly expose its ingredients. The facility to associate ingredients with drinks has moved up to the contract interface, in order to keep all the
OperationContract
methods in one place. It also permits me to illustrate some interface adapting between the contract and the native interfaces
1
.
[ DataContract ] public class DrinkDto { [ DataMember ] public string Name { get; set; } [ DataMember ] public string Method { get; set; } } [ DataContract ] public class IngredientDto { public enum Measurement { Fill, Measure, Drop, Tsp, } [ DataMember ] public string Name { get; set; } [ DataMember ] public Measurement Amount { get; set; } [ DataMember ] public int Qty { get; set; } } |
Listing 5 |
Planning a route
Adapting between the
RecipeBookContract
and
RecipeBook
interfaces is quite straightforward, but before getting to that, I want to briefly show the implementations of the WCF service and corresponding client applications, to illustrate some of the challenges with separating out the needs of the application from the requirements of WCF.
There are three main components to any WCF service, known as the ABCs:
- A is the address
- B is the binding
- C is the contract
We have already dealt with C, and the most flexible way of associating the contract with the binding is through a configuration file, such as that in listing 6.
<system.serviceModel> <services> <service name="Shaker.Service.RecipeBookService"> <endpoint binding="basicHttpBinding" contract= "Recipes.Contract.RecipeBookContract" /> </service> </services> </system.serviceModel> |
Listing 6 |
The service name in the
system.serviceModel
element is the fully-qualified type name of the implementing type of the contract (shown later in listing 8). The contract attribute of the endpoint element is the fully-qualified type name of the contract interface. Lastly for the configuration, this example uses the
basicHttpBinding
type which defines the communication method being exposed by the server.
One of the features of WCF is the ability to discover a service interface. The tool for this is svcutil.exe , which is also exposed inside Visual Studio as ‘Add Service Reference’ in the project tree. This tool is there to allow you to reference not only WCF services, but SOAP services on other platforms. Likewise, with certain restrictions, WCF services can be exposed to other platforms to consume. This is achieved by setting up a meta-data exchange (mex) endpoint in the service configuration, which uses SOAP standards to ‘explain’ to the client the details of the contract. svcutil generates the client-side configuration and proxy code to handle the communication layer.
However, this is much more than this simple application needs, and where all the communicating parts of an application are in .Net, a shared library referenced by both client and server with the interface for the contract is sufficient, and that’s what the example here uses. Besides which, I have a phobia about generated code.
You can also add the base address for the server in the same configuration, but for reasons of brevity – and because it’ll become useful later – the address for this server is directly in the server application code. This is a simple console application, shown in listing 7.
class ShakerServiceProgram { static int Main() { const string address = "http://localhost:5110"; var recipes = new RecipeBookService(); using( var host = new ServiceHost( recipes, new Uri( address ) ) ) { host.Open(); Console.WriteLine ( "Press [Enter] to close" ); Console.ReadLine(); } return 0; } } |
Listing 7 |
The heart of the application is the
ServiceHost
object, which uses the application configuration to determine the binding (and any other attributes which are defined in the configuration file), and to host the service. The default behaviour of a WCF service host is to create a single-threaded instance of the contract implementation (
RecipeBookService
, listing 8) for each call to the service. Other hosting options are available, including single-instance and per-session. Since this service is just using local collections to manage the objects to be stored and retrieved, it’s important that successive calls to the service communicate with the same instance. To achieve this, two things are needed. First, the implementing class is attributed with the
Single
instance mode, and secondly an actual instance of the type is passed to the
ServiceHost
object, instead of a type for it to instantiate as it sees fit.
Finally, it’s time to show the contract implementation. Listing 8 shows an example implementation of the
RecipeBookContract
service contract interface in listing 4. Note that the
IngredientDto
and
DrinkDto
types are already full classes – not interfaces – and can so be used directly.
[ ServiceBehavior( InstanceContextMode = InstanceContextMode.Single ) ] public class RecipeBookService : RecipeBookContract { public RecipeBookService() { drinks = new List< DrinkDto >(); ingredients = new Dictionary< string, List< IngredientDto > >(); } public IEnumerable< DrinkDto > AllDrinks() { return drinks; } public IEnumerable< string > AllIngredients() { return drinks.SelectMany( d => IngredientsOf ( d.Name ) ) .Select( i => i.Name ).Distinct(); } public IEnumerable< IngredientDto > IngredientsOf( string drink ) { return ingredients[ drink ]; } public void Add( DrinkDto drink, IEnumerable< IngredientDto > i ) { if( drinks.Any( d => d.Name == drink.Name ) ) { throw new FaultException ( "DuplicateDrink" ); } drinks.Add( drink ); ingredients.Add( drink.Name, i.ToList() ); } public IEnumerable< DrinkDto > WithIngredients ( IEnumerable< string > selectedIngredients ) { var result = drinks.Where ( drink => ingredients[ drink.Name ] .Any( dto => selectedIngredients.Contains ( dto.Name ) ) ); return result; } private readonly List< DrinkDto > drinks; private readonly Dictionary< string, List< IngredientDto > > ingredients; } |
Listing 8 |
The client application uses the same interface and data contract types to communicate with the server. A configuration is still required to set up the client-side WCF parts. The client configuration that corresponds to the server configuration in listing 6 is shown in listing 9.
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <client> <endpoint name="ShakerService" binding="basicHttpBinding" contract= "Recipes.Contract.RecipeBookContract" /> </client> </system.serviceModel> </configuration> |
Listing 9 |
The differences are subtle; instead of a services collection, there is a single client section, and the endpoint definition has a name. This name is used in the client program to identify the endpoint to which to connect.
Lastly, of course, we need code in the client application to set up the channel, talk to the service and consume the results, such as that in listing listing 10.
static class ShakerClientProgram { static int Main() { const string name = "ShakerService"; const string address = "http://localhost:5110"; using( var channel = new ChannelFactory < RecipeBookContract >( name ) ) { var recipes = channel.CreateChannel ( new EndpointAddress( address ) ); recipes.Add( new DrinkDto{ Name = "G&T", Method = "Mix with ice and lime" }, new[] { new IngredientDto{ Name = "Gin", Amount = IngredientDto.Measurement.Measure, Qty = 2 }, new IngredientDto{ Name = "Tonic Water", Amount = IngredientDto.Measurement.Fill, Qty = 1 } } ); foreach( var d in recipes.AllDrinks() ) { Console.WriteLine( d.Name ); } Console.WriteLine("Press [Enter] to exit"); Console.ReadLine(); return 0; } } } |
Listing 10 |
This code sets up a connection to the endpoint on which the server is listening using the name of the client-side endpoint definition in the configuration file, and uses the service contract. The
ChannelFactory
is parameterised with the contract interface type, associates it with the endpoint address specified, and uses the application config shown in listing 9 to configure the channel.
Client and server are separate programs, using WCF to communicate with each other. In this instance, they are console applications (for the server, the correct terminology is ‘self-hosting’), but could be Windows Forms, WPF or (for the server) an IIS-hosted web app.
Since they share the service contract definition in the
RecipeBookContract
interface, it makes sense to create a shared library which both console applications can reference. It would be possible to have the contract definition as a class in the server application, and have the client application reference the server assembly directly, but there are many reasons why this would be a bad idea; having one executable directly reference another is rarely a sign of good design.
Figure 1 shows the basic architecture of this application.
Figure 1 |
Back on track
Clearly, having the client side of the WCF application running as a process is less than ideal with respect to using it from other parts of your codebase. It would be better to make the client operate from a library. The problem with that is the channel configuration. The
ChannelFactory
shown in listing 10 is hard-wired to use the application configuration file. This is a convenience for the supposed default case (where the client is the application), but is here exposed as a liability. The superficial answer to that is to put all the WCF configuration into the
app.config
file of whatever application is hosting the client, and this works. However, it pollutes the application’s configuration, and means that the client
must
be hosted in an executable.
An alternative to that would be to remove all configuration from the client-side of the WCF channel. You can create a bare-bones channel, and set properties on it within the code, thus removing the need for a configuration file. This solution has some attractions, of course, but sometimes being able to fiddle with the settings without having to recompile the code is useful, perhaps even necessary.
Fortunately, Microsoft provide a solution. The
ConfigurationChannelFactory
is very similar to
ChannelFactory
except that it accepts a
System.Configuration
object in its constructor
2
. In conjunction with the almost impenetrable interfaces to the .Net application configuration system – with which details I shall not bore you – you can load any configuration file of the correct format at runtime. Armed with this, a library assembly can have its own configuration file, and load it internally to configure the WCF subsystem for an implementation of the client-facing interface.
The constructor shown in listing 11 shows how the configuration is loaded and associated with the channel. Setting the
ExeConfigFileName
property of the
filemap
object instructs the system to look for the file in the working directory. Other properties exist to tell it to look in various profile folders. The remainder of the class, in listing 12, adapts the WCF contract interface’s methods and objects into the client interface. All calls to the WCF service are wrapped in a delegate call to a private method that captures the common error handling.
public class RecipeBookClient : Recipes.RecipeBook { public RecipeBookClient( string address ) { const string name = "ShakerService"; var cfgMap = new ExeConfigurationFileMap { ExeConfigFilename = "RecipeBook.Client.Config" }; var config = ConfigurationManager .OpenMappedExeConfiguration ( cfgMap, ConfigurationUserLevel.None ); channel = new ConfigurationChannelFactory < RecipeBookContract > ( name, config, new EndpointAddress( address ) ); recipes = channel.CreateChannel(); } private readonly ConfigurationChannelFactory < RecipeBookContract > channel; private readonly RecipeBookContract recipes; } } |
Listing 11 |
Of more interest at this point is the implementation of the
Drink
interface first shown in listing 1 used by the
RecipeBookClient
in listing 12 to adapt between the native and contract interfaces. The implementation is in listing 13. Precisely why this was an interface should now be clear. Being able to implement it independently to communicate with a WCF service has great benefits for efficiently and effectively exposing the
ServiceContract
interface and its collaborators. Figure 2 shows the new architecture.
public IEnumerable< Drink > AllDrinks { get { return Call( () => recipes.AllDrinks() .Select( d => new DrinkAdapter ( recipes, d ) ) ); } } public IEnumerable< string > AllIngredients { get { return Call( () => recipes.AllIngredients() ); } } public void Add( params Drink[] newDrinks ) { foreach( var drink in newDrinks ) { Call( () => recipes.Add( new DrinkDto { Name = drink.Name, Method = drink.Method }, drink.Ingredients.Select ( i => new IngredientDto { Name = i.Name, Amount = ( IngredientDto.Measurement )i.Amount, Qty = i.Qty } ) ) ); } } public IEnumerable< Drink > WithIngredients ( params string[] s ) { return Call( () => recipes.WithIngredients( s ) .Select( d => new DrinkAdapter( recipes, d ) ) ); } public ResultType Call< ResultType > ( Func< ResultType > method ) { var result = default( ResultType ); Call( () => { result = method(); } ); return result; } public void Call( Action method ) { try { method(); } catch( FaultException x ) { switch( x.Message ) { case "DuplicateDrink": throw new DuplicateDrinkException ( x.Message ); } throw; } } |
Listing 12 |
public class DrinkAdapter : Drink { public DrinkAdapter( RecipeBookContract recipes, DrinkDto drinkDto ) { this.recipes = recipes; Name = drinkDto.Name; Method = drinkDto.Method; } public string Name { get; private set; } public string Method { get; private set; } public IEnumerable< Ingredient > Ingredients { get { return recipes.IngredientsOf( Name ) .Select( i => new Ingredient( i.Name, ( Measurement )i.Amount, i.Qty ) ); } } private readonly RecipeBookContract recipes; } |
Listing 13 |
Figure 2 |
It’s now possible to use the client code from anywhere – a hosting executable, part of a library of shared code, or a test case in a DLL. It still depends on having a running server process, so such a test isn’t really stand-alone, but it does allow us to provoke the code that uses the server. One of the benefits of this is that it is a test of the client configuration – a reasonably novel concept.
Going off-road
The next step is to try and isolate the server code into a shared library. The benefits here are largely about testing, but having the service code in a shared library allows you to defer the decision on whether to host it in a console application, a Windows Service or some other solution.
Moving the implementation of the
RecipeBookContract
from listing 8 into a shared library is easy enough: it can be copied as it stands, since it doesn’t use any WCF objects directly.
As with the
ChannelFactory
in the original client code (listing 10), the
ServiceHost
object in the server code (listing 7) is hard-wired to use the
app.config
for the WCF settings. There isn’t a corresponding
ConfigurationServiceHost
class which can be used in place of it, but we can derive from it and override it’s default behaviour. Listing 14 shows the new class.
public class RecipeBookHost : ServiceHost { public RecipeBookHost( string address ) { var cfgMap = new ExeConfigurationFileMap { ExeConfigFilename = "RecipeBook.Server.config" }; config = ConfigurationManager .OpenMappedExeConfiguration ( cfgMap, ConfigurationUserLevel.None ); var service = new RecipeBookService(); InitializeDescription( service, new UriSchemeKeyedCollection( new Uri ( address ) ) ); Open(); } protected override void ApplyConfiguration() { var section = ServiceModelSectionGroup.GetSectionGroup ( config ); if( section == null ) throw new ConfigurationErrorsException ( "Failed to find service model configuration" ); foreach( ServiceElement service in section.Services.Services ) { if( service.Name == Description.ConfigurationName ) base.LoadConfigurationSection ( service ); else throw new ConfigurationErrorsException ( "No match for description in Service model config" ); } } private readonly Configuration config; } |
Listing 14 |
The constructor sets up the configuration in much the same way as the
RecipeBook
-
Client
from listing 11.
The important method is the overridden
ApplyConfiguration
. This pulls the required WCF settings out of the configuration object and calls the base class’s
LoadConfigurationSection
to apply those settings.
ApplyConfiguration
is called from the
InitializeDescription
invocation in the constructor. If this hasn’t been done before
Open
is called, then the
app.config
is automatically loaded, so the order of operations is crucial. This class can be used in a hosting application such as that shown in listing 15, or (for example) a test case.
static class ShakerServiceProgram { static int Main() { const string address = "http://localhost:5110"; using( var host = new RecipeBookHost( address ) ) { Console.WriteLine( "v1 Shaker Service running. Press [Enter] to close" ); Console.ReadLine(); } return 0; } } |
Listing 15 |
Figure 3 shows the architecture of the client/server portion now.
Figure 3 |
Tests
With the new server and client library code we’ve now developed, it’s possible to write a truly automated test that has no requirements on external running code. Just as importantly, we can test them independently of any other code in an application (but not independently of each other - the client code
must
have a server to talk to). listing 16 shows a very simple test that creates an instance of the server, an instance of the client, and asserts that they can communicate. This is effectively testing the configurations of both client and server (an important thing, to be sure), but a much more interesting test would be to reproduce the tests we performed at the very beginning - by using the
DrinksCabinet
object.
[TestFixture, Explicit, Category("Service")] public class ClientServiceTest { [Test] public void ClientAndServiceStartAndCanCommunicate() { const string address = "http://localhost:5110"; using( var host = new RecipeBookHost ( address ) ) using( var recipes = new RecipeBookClient ( address ) ) { Assert.IsNotNull( host ); Assert.IsNotNull( recipes ); Assert.DoesNotThrow( () => { var x = recipes.AllDrinks; } ); } } } |
Listing 16 |
Recall from listing 2 that
DrinksCabinet
accepts an instance of the
RecipeBook
interface. Our new client code implements that interface, and adapts it to the WCF contract. It’s now possible to reproduce the
DrinksCabinetTests
from listing 3 but instead of using an instance of
LocalRecipeBook
, use an instance of
RecipeBook-Client
, as shown in listings 11 and 12.
Listing 17 shows the new tests using a locally running instance of the server, and the WCF implementation of the
RecipeBook
interface. Of course, the service implementation of the
RecipeBook
interface, and its client-side counterpart, can be tested in a similar way.
[ TestFixture, Explicit, Category( "Service" ) ] public class ServiceDrinksCabinetTests { private RecipeBookHost host; private Recipes.RecipeBook recipes; [ SetUp ] public void Start() { const string address = "http://localhost:5110"; host = new RecipeBookHost( address ); recipes = new RecipeBookClient( address ); } [ TearDown ] public void End() { recipes.Dispose(); host.Close(); } [Test] public void EmptyCabinetHasNoIngredients() { var cabinet = new DrinksCabinet(recipes); var results = cabinet.Ingredients; Assert.IsFalse(results.Any()); } [Test] public void CanLocateSpecificDrinkByName() { var cabinet = new DrinksCabinet(recipes); var expected = new LocalDrink("a", "", new[] { new Ingredient( "1", Measurement.Tsp, 1 ) }); var error = new LocalDrink("b", "", new[] { new Ingredient( "2", Measurement.Tsp, 1 ) }); recipes.Add(expected, error); var result = cabinet.Find(expected.Name); Assert.AreEqual(expected, result); } [Test] public void CanFilterDrinksOnNotSpecified() { var cabinet = new DrinksCabinet(recipes); var expected = new LocalDrink("a", "", new[] { new Ingredient( "1", Measurement.Tsp, 1 ) }); var error = new LocalDrink("b", "", new[] { new Ingredient( "2", Measurement.Tsp, 1 ) }); recipes.Add(expected, error); var results = cabinet.NotContaining("2"); Assert.AreEqual(expected, results.Single()); } } |
Listing 17 |
Conclusion
In this article, I’ve described a simple application, beginning from a basic native (and testable) interface and implementation, then showing how that interface and its collaborators could be transposed into a WCF service application. The flaws in the direct translation using a process to represent the server and client sides were that the implementation code wasn’t shareable easily with other applications, and that it wasn’t easily (and automatically) testable. The solution to both of those problems involved investigating WCF and by necessity, the .Net Configuration management systems to allow both server implementation and the client proxy adapter code to be exposed from their own shared assembly using their own configurations for the WCF subsystem. Being able to automatically test the WCF portions of your application should give you greater confidence that it works correctly – including the fact that the configuration is correct and sufficient – without having to run your application end-to-end to provoke it. Of course, that shouldn’t stop you from performing end-to-end testing!
Acknowledgements
Many thanks to Frances Buontempo for reading and commenting on initial drafts of this, and to Roger Orr and Chris Oldwood for valuable feedback on it.
References and source
[Fowler] Martin Fowler. Data transfer object. Technical report, http://martinfowler.com/eaaCatalog/dataTransferObject.html .
[Fowler02] Martin Fowler. Patterns of Enterprise Application Architecture . AddisonWesley, 2002.
[Love] Source code at: https://github.com/essennell/WcfTestingSecrets
[MSDN] MSDN(WCF). Windows communication foundation reference. Technical report, Microsoft, http://msdn.microsoft.com/en-us/library/dd456779.aspx .
[Resnick08] Bowen Resnick, Crane. Essential Windows Communication Foundation . Microsoft .Net Development Series. Addison Wesley, 2008. .Net 3.5.
[WCF] Microsoft(WCF). Windows communication foundation msdn articles. Technical report, Microsoft, http://msdn.microsoft.com/en-us/library/dd560536.aspx .