Unit-Testing WebClient Dependencies in Silverlight
If you are going to be interfacing with web services in Silverlight, there’s a good chance you’ll have classes that are dependent on System.Net.WebClient. Unfortunately, because of some of the security restrictions in Silverlight, you cannot mock or stub the System.Net.WebClient class. In addition, the constructor for DownloadStringAsyncCompleteEventArgs is internal, making it difficult to simulate the web client completing a call.
I started trying to test this completely within Silverlight by creating an interface and testable wrapper around the WebClient class. I ran into a dead end, though, when I tried to create the EventArgs classes. Silverlight will not let you create an instance of a class with an internal constructor via reflection because of the security restrictions.
I have chosen to deal with this particular issue by working around it by using a non-silverlight class library to run the tests. I’ll demo this with a fairly simple WebClient-dependent class but it will demonstrate the techniques for testing it.
First create a Silverlight Class Library that will contain the WebClient-dependent class. This is the class library you will use in your final Silverlight application. I called mine WebClientDependencyProject for this demo.
Then I added a simple class that is dependent on a WebClient. Note that it is not complete and does not deal with thread safety, but was concocted for demonstration purposes:
using System;
using System.Net;
namespace WebClientDependencyProject
{
/// <summary>
/// makes calls to a web service
/// </summary>
public class WebClientDependentClass : IDisposable
{
/// <summary>
/// fired when a call is completed
/// </summary>
public event EventHandler<DownloadStringCompletedEventArgs> CallCompleted;
private WebClient m_webClient;
/// <summary>
/// constructor
/// </summary>
/// <param name="webClient">the web client class to use</param>
public WebClientDependentClass(WebClient webClient)
{
m_webClient = webClient;
if (m_webClient == null)
throw new ArgumentNullException("webClient", "webClient cannot be null");
m_webClient.DownloadStringCompleted += webClient_DownloadStringCompleted;
}
/// <summary>
/// cancel the current web client call
/// </summary>
public void CancelCall()
{
m_webClient.CancelAsync();
}
/// <summary>
/// clean up
/// </summary>
public void Dispose()
{
m_webClient.DownloadStringCompleted -= webClient_DownloadStringCompleted;
}
/// <summary>
/// makes a call on the web client to the specified uri
/// </summary>
/// <param name="uri">the uri to use in the web call</param>
/// <param name="key">the key to return with the results</param>
public void MakeCall(Uri uri, Guid key)
{
m_webClient.DownloadStringAsync(uri, key);
}
private void webClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
EventHandler<DownloadStringCompletedEventArgs> temp = CallCompleted;
if (temp != null)
temp(this, e);
}
}
}
To test this class, create a new Windows (not Silverlight) Class Library.

I chose to name this library the same name as the Silverlight version, but added .WPF to the end of the name (WebClientDependencyProject.WPF) so that I could distinguish it from the Silverlight version.
Now right-click the new project and choose Add -> ExistingItem. In the Window that comes up, browse to the folder containing the Silverlight class library and select your WebClient-dependent class. Instead of choosing the Add button, click the arrow to the right side of the Add button and choose Add As Link. This will add a link to the Silverlight file rather than copying it:

Now you have the same code, but it will use the full .net framework classes and security settings rather then utilizing the restricted Silverlight versions. This opens up mocking the WebClient class as well as creating DownloadStringAsyncCompleteEventArgs instances.
In order to mock the WebClient class, I use Rhino Mocks 3.5.
Now you need to add one last project to your solution. This should be another windows (non-silverlight) class library that will contain the tests. I called mine WebClientDependencyProject_Tests:

Add a reference to the Rhino.Mocks library as well as the Microsoft.VisualStudio.QualityTools.UnitTestFramework.
Within this library you also have access to the full .net framework capabilities and do not have the Silverlight security restrictions, so mocking the WebClient class and simulating the DownloadStringCompleted events becomes fairly straightforward:
using System;
using System.Diagnostics;
using System.Net;
using System.Reflection;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
using WebClientDependencyProject;
namespace WebClientDependency_Tests
{
[TestClass]
public class WebClientDependencyClass_Tests
{
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void ConstructorShouldThrowOnNullWebClient()
{
WebClientDependentClass dependentClass = new WebClientDependentClass(null);
}
[TestMethod]
public void ConstructorShouldSubscribeToDownloadStringCompletedEvent()
{
WebClient webClient;
WebClientDependentClass dependentClass;
webClient = MockRepository.GenerateMock<WebClient>();
dependentClass = new WebClientDependentClass(webClient);
webClient.AssertWasCalled(
x => x.DownloadStringCompleted += Arg<DownloadStringCompletedEventHandler>.Is.NotNull);
dependentClass.Dispose();
}
[TestMethod]
public void DisposeShouldUnsubscribeFromEvent()
{
WebClient webClient;
WebClientDependentClass dependentClass;
webClient = MockRepository.GenerateMock<WebClient>();
dependentClass = new WebClientDependentClass(webClient);
webClient.AssertWasCalled(
x => x.DownloadStringCompleted += Arg<DownloadStringCompletedEventHandler>.Is.NotNull);
dependentClass.Dispose();
webClient.AssertWasCalled(
x => x.DownloadStringCompleted -= Arg<DownloadStringCompletedEventHandler>.Is.NotNull);
dependentClass.Dispose();
}
[TestMethod]
public void MakeCallShouldCallDownloadStringAsync()
{
WebClient webClient;
WebClientDependentClass dependentClass;
Uri uri;
Guid key = Guid.NewGuid();
webClient = MockRepository.GenerateMock<WebClient>();
uri = new Uri("http://www.inveigledsoftware.com");
dependentClass = new WebClientDependentClass(webClient);
dependentClass.MakeCall(uri, key);
Thread.Sleep(300);
webClient.AssertWasCalled(x => x.DownloadStringAsync(uri, key));
dependentClass.Dispose();
}
[TestMethod]
public void CancelProcessingShouldCallCancelAsync()
{
WebClient webClient;
WebClientDependentClass dependentClass;
Uri uri;
Guid key = Guid.NewGuid();
webClient = MockRepository.GenerateMock<WebClient>();
uri = new Uri("http://www.inveigledsoftware.com");
dependentClass = new WebClientDependentClass(webClient);
dependentClass.MakeCall(uri, key);
Thread.Sleep(300);
webClient.AssertWasCalled(x => x.DownloadStringAsync(uri, key));
dependentClass.CancelCall();
Thread.Sleep(300);
webClient.AssertWasCalled(x => x.CancelAsync());
dependentClass.Dispose();
}
[TestMethod]
public void DownloadStringCompletedFiresEvent()
{
WebClient webClient;
WebClientDependentClass dependentClass;
Uri uri;
ConstructorInfo[] constructors;
ConstructorInfo constructor;
bool wasCalled = false;
DownloadStringCompletedEventArgs returnedArgs = null;
DownloadStringCompletedEventArgs readArgs = null;
Guid key = Guid.NewGuid();
webClient = MockRepository.GenerateMock<WebClient>();
webClient.DownloadStringCompleted += (sender, e) =>
{
wasCalled = true;
returnedArgs = e;
};
constructors =
typeof(DownloadStringCompletedEventArgs).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
Debug.Assert(constructors != null);
constructor = constructors[0];
object args = constructor.Invoke(new object[] { null, null, false, key });
Debug.Assert(args != null);
readArgs = (DownloadStringCompletedEventArgs)args;
uri = new Uri("http://www.google.com");
dependentClass = new WebClientDependentClass(webClient);
dependentClass.MakeCall(uri, key);
Thread.Sleep(300);
webClient.AssertWasCalled(x => x.DownloadStringAsync(uri, key));
dependentClass.CallCompleted += dependentClass_callCompleted;
webClient.Raise(x => x.DownloadStringCompleted += null, this, readArgs);
Assert.IsTrue(wasCalled);
Assert.IsNotNull(returnedArgs);
Assert.IsTrue(m_eventRaised);
Assert.AreEqual(m_args, readArgs);
Assert.AreEqual(key, m_args.UserState);
dependentClass.CallCompleted -= dependentClass_callCompleted;
dependentClass.Dispose();
}
private bool m_eventRaised;
private DownloadStringCompletedEventArgs m_args;
private void dependentClass_callCompleted(object sender, DownloadStringCompletedEventArgs e)
{
m_eventRaised = true;
m_args = e;
}
}
}
If you end up sharing many files between class libraries, Microsoft has created the Project Linker tool to help keep the WPF/Silverlight projects that share files in sync:
Project Linker
UPDATE Dec. 4, 2009:
In Silverlight4/Visual Studio 2010, it looks like you will not have to make duplicate WPF projects to allow testing your Silverlight libraries. This would eliminate having to create the WebClientDependencyProject.WPF project in the above example. More information here:
Sharing Silverlight Assemblies with .NET Apps

Leave a Reply