Testing WCF Service Apps (part 4 of 4)

Previous Posts:
Part 0 of 4: Introduction
Part 1 of 4: Testing the Service
Part 2 of 4: Testing the Client
Part 3 of 4: Testing the Asynchronous Client

Shout it

Functional Testing the WCF Application

In functional testing, the goal is to test as much of the application that you can to determine that it does what you want from a functional perspective.  It differs greatly from unit testing in that a unit test is only concerned with an individual class.  Functional tests are concerned with testing the interactions of the objects in the system from the user input to the user output.

I thought it would be easiest to draw out what the data mining application is doing in terms of data flow.  This particular application is a data mining application which queries a service to pull out data that the user wants.  In this case, the question that the user wants answered is "What recipes exist in the database that include a given ingredient?"  The data flow goes like this:

App.Diagram

The user types an ingredient into the console.  The console launches our application and calls "Main" with the arguments that include the ingredient in question.  The "Main" routine creates an IngredientFinder object which requests a list of all recipes known to the service.  It does this by asking the service proxy which uses WCF to ask the actual RecipeService, which may exist anywhere on the planet.  The actual RecipeService asks the Business Objects which in turn queries the database for all known recipes.  The database returns the results to the Business Objects which in turn returns the results to the RecipeService.  Those results travel through the WCF infrastructure back to the service proxy in the client.  The IngredientFinder filters the recipes for the requested ingredient and returns the results to the "Main" method which then writes the results back to the console for the user to read.

From a testing perspective, the only code we are responsible for in this system is Main, IngredientFinder, RecipeService and Business Objects.  The user and console are completely external to our application.  The service proxy is auto-generated by Visual Studio and the WCF framework is part of the .NET framework.  We do not need to test these parts.  Finally, the database is also a Microsoft product (SQL Server) and we can trust that it works correctly as well.  We need to eliminate all components in this system that we do not control, thus focus on testing the code in which we do control.

Eliminating the User and Console

Traditionally our "Main" method would instantiate our IngredientFinder (see part 1) with the service proxy, get the result and write it out to the console:

static void Main(string[] args)
{
    var finder = new IngredientFinder(new RecipeBoxServiceClient());

    foreach (RecipeData recipe in finder.GetRecipes(args[0]))
        Console.WriteLine(recipe.Title);

    Console.ReadKey();
}

The problem with this is that Console is a static class and cannot be replaced as-is.  Instead, we need to extract this as an interface and pass the interface in:

public interface IConsoleOutput
{
    void WriteLine(string line);
    ConsoleKeyInfo ReadKey();
}

private class ConsoleOutput : IConsoleOutput
{
    public void WriteLine(string line) { Console.WriteLine(line); }
    public ConsoleKeyInfo ReadKey() { return Console.ReadKey(); }
}

By doing this, we can now replace the Console.WriteLine static method with a spy in our test (later).  We will create a new static method named Execute which passes in the IConsoleOutput and IRecipeBoxService interfaces. Our new "Main" routine will look like this:

static void Main(string[] args)
{
    Execute(new ConsoleOutput(), new RecipeBoxServiceClient(), args);
}

public static void Execute(IConsoleOutput console, IRecipeBoxService service, string[] args)
{
    var finder = new IngredientFinder(service);

    foreach (RecipeData recipe in finder.GetRecipes(args[0]))
        console.WriteLine(recipe.Title);

    console.ReadKey();
}

Eliminating the WCF Infrastructure

Now that the user and console have been abstracted out, we can start thinking about testing the Execute method.  The problem we have now is that IRecipeBoxService is an automatically generated interface within the client’s namespace.  We have an implementation of this interface in the service, but it is defined in the service’s namespace.  The two interfaces are not compatible.  We want to eliminate the need for WCF, so we cannot use the generated proxy class.  What we need here is a bridge class:

public class ServiceWrapper : DataMining.RecipeBoxService.IRecipeBoxService
{
    private readonly Services.IRecipeBoxService _source;

    public ServiceWrapper(Services.IRecipeBoxService source)
    {
        _source = source;
    }

    private static ToType TranslateData<FromType, ToType>(FromType source) where ToType : class
    {
        var serverSerializer = new DataContractSerializer(typeof(FromType));
        var clientSerializer = new DataContractSerializer(typeof(ToType));

        using (var stream = new MemoryStream())
        {
            serverSerializer.WriteObject(stream, source);
            stream.Flush();
            stream.Position = 0;

            return clientSerializer.ReadObject(stream) as ToType;
        }
    }

    public DataMining.RecipeBoxService.RecipeData[] AllRecipes()
    {
        var result = new List<DataMining.RecipeBoxService.RecipeData>();
        foreach (var data in _source.AllRecipes())
            result.Add(TranslateData<Services.DataContracts.RecipeData, DataMining.RecipeBoxService.RecipeData>(data));
        return result.ToArray();
    }

    ...
}

This class bridges the client interface over to the service interface.  WCF interfaces are made up of Service Contracts and Data Contracts.  The Service Contract is the functional interface where the Data Contract defines what the data looks like.  The WCF framework uses the System.Runtime.Serialization.DataContractSerializer to transfer the data in plain-text.  In this ServiceWrapper class, the TranslateData function uses the DataContractSerializer to serialize the data to and from the client and service data types.  This class is the glue that replaces WCF from the testing process.  In a sense, this class is a very simple implementation of the WCF concepts.

Putting it all Together

The only piece of functionality still out of our control is the database.  I am going to replace the SQL Server with a temporal, in-memory database for testing.  This post will not go into the details, but my previous post on Blah Blah Blah talks about how to do this.  Now, our diagram looks shows a system where every piece of code is in our control:

App.Testing

Writing our Functional Tests

The only code we still need before we start writing functional tests is the ConsoleOutputSpy.  It captures the output in a list of strings that we can verify against.

public class ConsoleOutputSpy : Program.IConsoleOutput
{
    public List<string> Output { get; private set; }

    public ConsoleOutputSpy()          { Output = new List<string>(); }
    public void WriteLine(string line) { Output.Add(line); }
    public ConsoleKeyInfo ReadKey()    { return new ConsoleKeyInfo(); }
}

Now that we have our console output spy, we can look at our test SetUp:

private ConsoleOutputSpy _consoleOutput;
private DataMining.RecipeBoxService.IRecipeBoxService _service;

[SetUp]
public void SetUp()
{
    _service = new ServiceWrapper(new RecipeBoxService(new MockBackEndConfiguration()));
    _consoleOutput = new ConsoleOutputSpy();
}

Finally, we can write our tests.  This test will populate the mock database with four ingredients and three recipes.  Only two of the recipes include the "Cheese" ingredient, so we can test that our data miner will return only those two recipes that contain "Cheese":

[Test]
public void SimpleTest()
{
    AddIngredients("Macaroni", "Cheese", "Bread", "Peanut Butter");
    AddRecipeToDatabase("Mac & Cheese", "Macaroni", "Cheese");
    AddRecipeToDatabase("Grilled Cheese", "Bread", "Cheese");
    AddRecipeToDatabase("Peanut Butter Sandwich", "Bread", "Peanut Butter");

    Program.Execute(_consoleOutput, _service, new [] {"Cheese"});

    Assert.That(_consoleOutput.Output.Count, Is.EqualTo(2));
    Assert.That(_consoleOutput.Output[0], Is.EqualTo("Mac & Cheese"));
    Assert.That(_consoleOutput.Output[1], Is.EqualTo("Grilled Cheese"));
}

Conclusion

This concludes my four-part series on testing WCF applications.  I have covered unit testing the service, unit testing the client, unit testing an asynchronous client and finally, functional testing the entire application. 

It is important for me to note that these techniques have served me very well in the real-world.  I have a Silverlight application which communicates asynchronously with a back-end WCF service.  These techniques have allowed us to write tests that cover our application from all aspects.  The tests we have in place run extremely quickly and are robust because they do not rely on any services running on a separate machine.

One Response to “Testing WCF Service Apps (part 4 of 4)”

  1. Hi Brian,

    Thank you for the last part in this series. It’s very helpful.

    You mentioned, “This post will not go into the details, but my previous post on Blah Blah Blah talks about how to do this.”, but I expected a link instead of “Blah Blah Blah”.

    Thank you! :)

Leave a Reply