Unit testing in Silverlight part 2

Part 1
Part 2
Part 3
Part 4

In my previous post I described the basic setup of unit testing in Silverlight, you can read all about it here.

The basics where very nice but lots of code we write in Silverlight has to do with networking and in Silverlight that means asynchronous code, something that is always hard to test!

Because, just I like using Flickr, lets download some pictures and see what it takes to make that testable. Below is the very simple application we need to unit test, basically a list of pictures from my Flickr photo stream.

image

Nothing fancy, just a ListBox with pictures [:)]

The code in the page is no big deal either and looks like this:

public partial class Page : UserControl
{
    public Page()
    {
        InitializeComponent();
        Loaded += new RoutedEventHandler(Page_Loaded);
    }

    void Page_Loaded(object sender, RoutedEventArgs e)
    {
        PhotoCollection photoCollection = 
            (PhotoCollection)Resources["PhotoCollectionDS"];
        FlickrRequest request = new FlickrRequest();

        request.PhotoSearchByUser("97044050@N00", 
            (photo) => photoCollection.Add(photo));
    }
}


Basically it just retrieves the PhotoCollection, creates a FlickrRequest object and tells it to search for a users photos and add each photo found to the collection. The PhotoCollection is no big deal either and is just a ObservableCollection<Photo>.



The interesting stuff is in the FlickrRequest class. This is the one that needs to be unit tested and looks like this:



public class FlickrRequest
{
    private const string _flickrUrl = "http://api.flickr.com/services/rest";

    public void PhotoSearchByUser(string userId, Action<Photo> action)
    {
        string url = string.Format(
            @"{0}/?method={1}&api_key={2}&user_id={3}&extras=original_format",
            _flickrUrl, "flickr.photos.search", App.FlickrApiKey, userId);

        WebClient client = new WebClient();
        client.OpenReadCompleted += (sender, e) =>
        {
            if (e.Error == null)
            {
                FlickrResponse response = DeserializeResult(e.Result);
                if (response.Status == "ok")
                {
                    foreach (var photo in response.Photos.Photos)
                        action(photo);

                    if (response.Photos.Page < response.Photos.PageCount)
                    {
                        string nextUrl = string.Format("{0}&page={1}", 
                            url, response.Photos.Page + 1);
                        client.OpenReadAsync(new Uri(nextUrl));
                    }
                }
            }
        };

        client.OpenReadAsync(new Uri(url));
    }

    public static FlickrResponse DeserializeResult(Stream stream)
    {
        FlickrResponse response = null;
        try
        {
            XmlSerializer ser = new XmlSerializer(typeof(FlickrResponse));
            response = (FlickrResponse)ser.Deserialize(stream);
        }
        catch (InvalidOperationException)
        {
            response = new FlickrResponse();
        }

        if (response.Photos == null)
            response.Photos = new ResponsePhotoCollection();
        if (response.Photos.Photos == null)
            response.Photos.Photos = new Photo[] { };

        return response;
    }
}


The PhotoSearchByUser function here is the interesting one as it uses the WebClient class and the Flickr REST API to download the photo information. This code also has the complexity that not all the data is returned from the first call but we need to do multiple calls to get all the photo information. Creating a unit test should be straightforward expect for the fact that we do not want to depend on Flickr and fake out the networking parts. After all if we didn’t it would be a integration test instead of a unit test. So we need to somehow fake the networking behavior.



With regular .NET code I use TypeMock to help out with unit testing and I am sure that would make live easy here but unfortunately there is no Silverlight version of TypeMock yet. In fact, as far as I am aware, there is not a single mock framework for use with Silverlight. So we need to do this some other way.



First we need to get rid of the WebClient class because non of its methods are virtual and we cannot substitute another type. So the first thing I did was create a TestableWebClient, which looks just like the original WebClient only it allows me to change the behavior for testing purposes. The TestableWeblient looks like this:



public class TestableWebClient
{
    public event EventHandler<TestableOpenReadCompletedEventArgs> OpenReadCompleted;

    public virtual void OpenReadAsync(Uri address)
    {
        WebClient client = new WebClient();
        client.OpenReadCompleted += (s, e) =>
            {
                OnOpenReadCompleted(e);
            };
        client.OpenReadAsync(address);
    }

    protected void OnOpenReadCompleted(OpenReadCompletedEventArgs e)
    {
        TestableOpenReadCompletedEventArgs args = 
            new TestableOpenReadCompletedEventArgs(e); OnOpenReadCompleted(args); } protected void OnOpenReadCompleted(TestableOpenReadCompletedEventArgs e) { if (OpenReadCompleted != null) { OpenReadCompleted(this, e); } } }


Unfortunately the OpenReadCompletedEventArgs type doesn’t have a public constructor so I cannot create one myself so I have to use a TestableOpenReadCompletedEventArgs instead. This class looks like this:



public class TestableOpenReadCompletedEventArgs : AsyncCompletedEventArgs
{
    public TestableOpenReadCompletedEventArgs(
        Stream result, Exception error, bool cancelled, object userState)
        : base(error, cancelled, userState)
    {
        Result = result;
    }

    public TestableOpenReadCompletedEventArgs(
        OpenReadCompletedEventArgs args)
        : base(args.Error, args.Cancelled, args.UserState)
    {
        Result = args.Result;
    }

    public Stream Result { get; private set; }
}


So with these new classes I can change the PhotoSearchByUser function to use the new class and it now looks like this:



public void PhotoSearchByUser(string userId, Action<Photo> action)
{
    string url = string.Format(
        @"{0}/?method={1}&api_key={2}&user_id={3}&extras=original_format",
        _flickrUrl, "flickr.photos.search", App.FlickrApiKey, userId);

    TestableWebClient client = new TestableWebClient();
    client.OpenReadCompleted += (sender, e) =>
    {
        if (e.Error == null)
        {
            FlickrResponse response = DeserializeResult(e.Result);
            if (response.Status == "ok")
            {
                foreach (var photo in response.Photos.Photos)
                    action(photo);

                if (response.Photos.Page < response.Photos.PageCount)
                {
                    string nextUrl = string.Format("{0}&page={1}", 
                        url, response.Photos.Page + 1);
                    client.OpenReadAsync(new Uri(nextUrl));
                }
            }
        }
    };

    client.OpenReadAsync(new Uri(url));
}










Not much changes, in fact only a single line changed but we are not quite there yet. We still need a way to get our unit tests to use a different TestableWebClient implementation. Do do so I will pass in the type of TestableWebClient to use with the FlickrRequest constructor. Check the new code:



public class FlickrRequest
{
    private string _flickrUrl = "http://api.flickr.com/services/rest";
    private Type _webClientType = typeof(TestableWebClient);

    public FlickrRequest()
    {
    }

    public FlickrRequest(Type webClientType)
    {
        _webClientType = webClientType;
    }

    public void PhotoSearchByUser(string userId, Action<Photo> action)
    {
        string url = string.Format(
            @"{0}/?method={1}&api_key={2}&user_id={3}&extras=original_format",
            _flickrUrl, "flickr.photos.search", App.FlickrApiKey, userId);

        TestableWebClient client = (TestableWebClient)
            Activator.CreateInstance(_webClientType); // Rest of the code left out. } }

Now we can start testing this code [:)]

I created two unit tests for this code as an example, they look like this:



[TestClass]
public class FlickrDownloadTest : SilverlightTest
{
    [TestMethod]
    [Asynchronous]
    public void Test1()
    {
        FlickrRequest request = new FlickrRequest(typeof(MockTestableWebClient));

        request.PhotoSearchByUser("97044050@N00", (photo) => EnqueueTestComplete());
    }
    
    [TestMethod]
    [Asynchronous]
    public void Test2()
    {
        int pageCount = 0;
        FlickrRequest request = new FlickrRequest(typeof(MockTestableWebClient));

        request.PhotoSearchByUser("97044050@N00", (photo) => pageCount++);
        EnqueueConditional(() =>
            {
                return pageCount == 5;
            });
        EnqueueTestComplete();
    }
}


The fake, or mock, WebClient looks like this:



public class MockTestableWebClient : TestableWebClient
{
    private int _page = 0;

    public override void OpenReadAsync(Uri address)
    {
        _page++;

        FlickrResponse response = new FlickrResponse();
        response.Status = "ok";
        response.Photos = new ResponsePhotoCollection();
        response.Photos.Page = _page;
        response.Photos.PageCount = 5;
        response.Photos.Photos = new Photo[] { new Photo() };
        Stream stream = SerializeResult(response);
        stream.Position = 0;
        TestableOpenReadCompletedEventArgs args = 
            new TestableOpenReadCompletedEventArgs(stream, null, false, null);
        OnOpenReadCompleted(args);
    }


    public static Stream SerializeResult(FlickrResponse response)
    {
        MemoryStream stream = new MemoryStream();
        XmlSerializer ser = new XmlSerializer(typeof(FlickrResponse));
        ser.Serialize(stream, response);

        return stream;
    }
}






Basically I just return a response stating that there are 5 pages and each contains a single blank photo.



Running these tests produces the following output showing both tests passed [:)]



image



 



Am I happy with the changes I had to make to the Silverlight app to make it unit testable? Well yes and no. I didn’t like having to create a wrapper class for the WebClient and the OpenReadCompletedEventArgs. Bit now that I have done so creating using other test for code using the WebClient is a lot easier. In this case I only added the OpenReadAsync() function but doing the same with the other functions is a breeze. And the end result is I could unit test the code in PhotoSearchByUser() pretty much the way I wrote it without having to change the code a lot just to make it unit testable and that is good! So overall I am reasonably happy with the result [:)]



I pretty much skipped all details of the actual unit testing code itself. Now there is some new stuff so in a future blog post I am going to explain what I did in the unit tests themselves.



And thanks to Jeff Wilcox for providing some useful feedback.



Enjoy!



 



Update: Part 1, Part 2, Part 3.



 



[f1]
[f2]

3 thoughts on “Unit testing in Silverlight part 2

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>