Geeks With Blogs

News Locations of visitors to this page
Brian Genisio's House of Bilz

Previous posts:
Part 0 of 4: Introduction
Part 1 of 4: Testing the Service
Shout it kick it on DotNetKicks.com

Testing the Client

So far, I outlined how to test your WCF service.  I simply took advantage or the WCF architecture and tested the service directly outside of the actual service harness.  Now I need to set my sights on the client.  This becomes a bit more difficult, but I wouldn't say that it is necessarily hard.  I will start by giving a typical textbook example of hooking up to our service, and then I will tell you what is wrong with it.  I will continue by modifying the code to be more testable so that the service can be mocked. 

A Textbook Example

Most WCF tutorials and books have you start by adding a service reference to your service.  Doing this will generate a proxy client that you can use in your application.  My client application is a very simple data mining application.  You can give it the ingredient name and the application will return all recipes that include the ingredient of choice.  The first step is creating the service reference.  Right-click on the project and select "Add Service Reference".

AddServiceReference

Once the service has been added, I can use it in my code:

public class IngredientFinder
{
    public IEnumerable<RecipeData> GetRecipes(string ingredientName)
    {
        var recipeService = new RecipeBoxServiceClient();

        return from recipe in recipeService.AllRecipes() 
               where recipe.ContainsIngredient(ingredientName) 
               select recipe;
    }
}

This is all I need to get my app up and running. My main function just calls into this code and prints the results.  There is, however, one major flaw with this code: it is not testable!  This is because if I were to instantiate an instance of IngredientFinder, I would be required to hook up to a WCF service via the RecipeBoxServiceClient.  Technically I could write this code but I wouldn't recommend it.  For one, it requires a lot of setup to harness the service.  Secondly, it drifts away from being a unit test and becomes more of a functional or integration test (more on this in part 4).  Thirdly, you can't always assume that you control the service.  Assume, for instance, that you are writing an application that connects to a service like Twitter.  You certainly don't want your unit tests hitting the only instance of the service: the live one.

We need to do something about this...

Making it Testable

If you were to inspect the RecipeBoxServiceClient class that was generated for you from the service specification, you would find one very important detail:  RecipeBoxServiceClient implements the auto-generated interface IRecipeBoxService.  Let us make a modification to the IngredientFinder to make it testable:

public class IngredientFinder
{
    private readonly IRecipeBoxService _recipeService;

    public IngredientFinder(IRecipeBoxService recipeService)
    {
        if(recipeService == null) throw new ArgumentNullException("recipeService");

        _recipeService = recipeService;
    }

    public IEnumerable<RecipeData> GetRecipes(string ingredientName)
    {
        return from recipe in _recipeService.AllRecipes() 
               where recipe.ContainsIngredient(ingredientName) 
               select recipe;
    }
}

This is a classic example of dependency injection.  The user of the class (in our case, the main method) is now responsible for defining the service to use.  This way, the IngredientFinder doesn't need to know anything about the connection details.  In addition, I can write tests that mock out the service completely.

Writing Tests Against the Client

In my example, I am using Rhino Mocks, but you can use any mock/fake/stub framework/method you wish.

[TestFixture]
public class TestIngredientFinder
{
    private MockRepository _mocks;
    private IRecipeBoxService _mockService;
    private IngredientFinder _finder;

    [SetUp]
    public void SetUp()
    {
        _mocks = new MockRepository();
        _mockService = _mocks.CreateMock<IRecipeBoxService>();
        _finder = new IngredientFinder(_mockService);
    }

    [Test]
    public void Test_IngredientFinder_With_Cheese()
    {
        Expect.Call(_mockService.AllRecipes()).
            Return(new[] {Recipe("Mac&Cheese", "Macaroni", "Cheese")});
        _mocks.ReplayAll();        

        var recipes = _finder.GetRecipes("Cheese").ToArray();

        Assert.That(recipes.Length, Is.EqualTo(1));
        Assert.That(recipes[0].Title, Is.EqualTo("Mac&Cheese"));
    }

    [Test]
    public void Test_IngredientFinder_With_Two_Recipes_That_Have_Cheese()
    {
        Expect.Call(_mockService.AllRecipes()).Return(new[]{
                                                              Recipe("Mac&Cheese", "Macaroni", "Cheese"),
                                                              Recipe("Grilled Cheese", "Cheese", "Bread")
                                                          });
        _mocks.ReplayAll();

        var recipes = _finder.GetRecipes("cheese").ToArray();

        Assert.That(recipes.Length, Is.EqualTo(2));
        Assert.That(recipes[0].Title, Is.EqualTo("Mac&Cheese"));
        Assert.That(recipes[1].Title, Is.EqualTo("Grilled Cheese"));
    }

    [Test]
    public void Test_IngredientFinder_Finding_Nothing()
    {
        Expect.Call(_mockService.AllRecipes()).Return(new[]{
                                                              Recipe("Mac&Cheese", "Macaroni", "Cheese"),
                                                              Recipe("Grilled Cheese", "Cheese", "Bread")
                                                          });
        _mocks.ReplayAll();

        var recipes = _finder.GetRecipes("chicken").ToArray();

        Assert.That(recipes.Length, Is.EqualTo(0));
    }

    [Test, ExpectedException(typeof(ArgumentNullException))]
    public void Test_For_Null()
    {
        _finder.GetRecipes(null);
    }

    [Test, ExpectedException(typeof(ArgumentNullException))]
    public void Test_Constructor_With_Null_Service_Interface()
    {
        var junk = new IngredientFinder(null);
    }
}

With this test suite, I have full coverage on my IngredientFinder class and I never needed to instantiate the actual service. 

Next time

I will discuss how to test your client code with asynchronous service references.  It turns out that it is not as straight-forward as the synchronous approach (this post), so I will devote an entire post to the asynchronous case. (Part 3 of 4)

Posted on Saturday, November 29, 2008 2:48 PM | Back to top


Comments on this post: Testing WCF Service Apps (Part 2 of 4)

# re: Testing WCF Service Apps (Part 2 of 4)
Requesting Gravatar...
Good post!

For automated testing of WCF services, I've found WCFStorm (http://www.wcfstorm.com) really useful. It can dynamically invoke services even those containing complex parameters. Plus it has support for all the WCF bindings (netTcpBinding, wsHttpBinding etc.) even including custom and user-defined bindings.
Left by Alex on Jul 30, 2009 8:25 PM

# re: Testing WCF Service Apps (Part 2 of 4)
Requesting Gravatar...
Could you explain to me how the above could take into account calling close or dispose on the service if you use the interface as opposed to the proxy which inherits from the clientbase<IRecipeBoxService> ?
Left by A'braham Barakhyahu on Aug 21, 2009 10:40 AM

# re: Testing WCF Service Apps (Part 2 of 4)
Requesting Gravatar...
A'braham,

I have a few thoughts on that.

First, if all you want to do is call dispose, your client code can just cast as IDisposable and dispose it:
if(_recipeService is IDisposable)
(_recipeService as IDisposable).Dispose();

Of course, you probably want something a bit more explicit. For that, I would point you to part-3 of this series where we tack on an interface that includes the things they forgot to include in the interface:

public interface IRecipeBoxServiceComplete : IDisposable
{
void Close();
}

public partial class RecipeBoxServiceClient : IRecipeBoxServiceComplete { }

You can add anything else that you need from ClientBase<IRecipeBoxService> to IRecipeBoxServiceComplete with this.

Now, you can call Close or Dispose from the interface:

IRecipeBoxServiceComplete _service = new RecipeBoxServiceClient();
_service.Close();
_service.Dispose();

I have used this technique MANY times, and it works really well. It would be nice if the interfaces from the auto-generated code were more complete... but at least there is a way to hack around it.

Does this help?
Left by Brian Genisio on Aug 21, 2009 3:27 PM

# re: Testing WCF Service Apps (Part 2 of 4)
Requesting Gravatar...
Thanks. Seems like it would make sense to make a interface of clientbase and have your service interfaces inherit from that.
Left by A'braham Barakhyahu on Aug 24, 2009 3:16 AM

# re: Testing WCF Service Apps (Part 2 of 4)
Requesting Gravatar...
A'braham,

Yes, I completely agree. I think that making an IClientBase<T> is the best course of action. Then, you can create the interface like this:

public interface IRecipeBoxServiceComplete : IClientBase<IRecipeBoxService>
{
...
}
Left by Brian on Aug 24, 2009 3:34 AM

# re: Testing WCF Service Apps (Part 2 of 4)
Requesting Gravatar...
Great post!

I had the same question as A'braham and went ahead an implemented a close() method on the service as suggested by you. but here's my dilemma - I believe in the principle that If you inject the IRecipeBoxService into your class, your class should not be closing or opening the service because your class does not own the service. The service proxy was given to you by an external class and that external object should be responsible for opening and closing the service.

Closing the service is important but I don't know how we can achieve that without violating the principle above and still make the class testable.

hope this makes sense and I would love to hear your thoughts on it.
Left by Ash on Sep 21, 2009 6:40 AM

# re: Testing WCF Service Apps (Part 2 of 4)
Requesting Gravatar...
Hi Brian your example is good but I feel is missing something - Closing the channel after use. If you simply inject the service interface you will not be able to invoke Close(), which is a recommended practise. Thus I feel one needs to create a custom interface that extends the service interface, but does a bit more too. What do you think?

Tamim.

Left by Tamim on Jun 06, 2010 12:17 AM

# re: Testing WCF Service Apps (Part 2 of 4)
Requesting Gravatar...
Wonderful views and discussions, I will my great, thank you! ...
Left by jim rice jerseys on Aug 19, 2011 1:30 PM

Your comment:
 (will show your gravatar)


Copyright © Brian Genisio's House Of Bilz | Powered by: GeeksWithBlogs.net