Helper classes for mock testing WebClient
The WebClient class in .NET and Silverlight is used for fetching (GET) resources from the web or sometimes locally. This scenario inherently does not lend itself very well to unit testing since it means the component being written is dependent on an external resource. Unit testing seeks to test the component by itself and eliminate the request to the external resource. Mock testing comes to our rescue in such scenarios by letting us mock the touch-points where the component interfaces with the external resource. But, the use of WebClient to make the request to the external resource makes mocking tricky, since WebClient does not implement interfaces or virtual methods to expose its functionality. Most of the widely used mocking frameworks can only mock members of an interface or virtual methods. To overcome this short-comming, I have created and used the following helpers classes to help mock web requests made using WebClient. It consists of a ‘WrappedWebClient’ class that wraps the methods supported by WebClient and implements interfaces defined alongside to help with the mocking of those methods.
The sample below contains a service class that fetches stories from the Digg web service using a search term.
- public interface IServiceRequest<T, K>
- {
- void Run( T input );
- event EventHandler<ServiceRequestEventArgs<K>> SelectionCompleted;
- object State
- {
- get;
- set;
- }
- }
- public class DiggSearchService : IServiceRequest<string, IEnumerable<DiggStory>>
- {
- string template = “http://services.digg.com/search/stories?query={0}&count={1}&appkey=http://www.vishcio.us”;
- public IWebDownloader WebDownloader
- {
- get;
- set;
- }
- public int StoryCount
- {
- get;
- set;
- }
- public DiggSearchService()
- {
- WebDownloader = new WrappedWebClient();
- StoryCount = 10;
- }
- #region IServiceRequest<string,IEnumerable<DiggStory>> Members
- public void Run( string input )
- {
- WebDownloader.DownloadStringCompleted += new EventHandler<WrappedDownloadStringCompletedEventArgs>( WebDownloader_DownloadStringCompleted );
- Uri address = new Uri(string.Format(template, input,StoryCount));
- WebDownloader.DownloadStringAsync( address, State );
- }
- void WebDownloader_DownloadStringCompleted( object sender, WrappedDownloadStringCompletedEventArgs e )
- {
- if( e.Error != null )
- {
- RaiseSelectionCompletedEvent( null, e.Error );
- return;
- }
- XDocument xmlStories = XDocument.Parse( e.Result );
- IEnumerable<DiggStory> stories = from story in xmlStories.Descendants( “story” )
- select new DiggStory
- {
- Id = ( int ) story.Attribute( “id” ),
- Title = ( ( string ) story.Element( “title” ) ).Trim(),
- Description = ( ( string ) story.Element( “description” ) ).Trim(),
- HrefLink = new Uri( ( string ) story.Attribute( “link” ) ),
- NumDiggs = ( int ) story.Attribute( “diggs” ),
- UserName = ( string ) story.Element( “user” ).Attribute( “name” ).Value,
- };
- RaiseSelectionCompletedEvent( stories, null );
- }
- public event EventHandler<ServiceRequestEventArgs<IEnumerable<DiggStory>>> SelectionCompleted;
- public object State
- {
- get;
- set;
- }
- #endregion
- private void RaiseSelectionCompletedEvent( IEnumerable<DiggStory> data, Exception ex )
- {
- if( SelectionCompleted != null )
- SelectionCompleted( this, new ServiceRequestEventArgs<IEnumerable<DiggStory>>( data, ex ) );
- }
- }
The service class above uses the WrappedWebClient instead of the WebClient class itself. This enables the service class to be unit tested by mocking out the web request to the digg service. The WrappedWebClient implements the following interfaces
- IWebDownloader
- IWebUploader
- IWebReader
- IWebWriter
This enables us to mock the various types of operations that can be performed by the WebClient. The Digg service class above exposes an IWebDownloader property which makes it clear that the service class uses the ‘DownloadStringAsync’ method on the WebClient to access the service. To test the service class above, a sample response for the request can be downloaded beforehand and saved as a XML file. This file can then be compiled as a ‘Resource’ into the test assembly. During the test, the XML file can be retrieved from the test assembly and can be used as the return value from the mock object for the IWebDownloader interface. The Silverlight unit test below illustrates how the service class can be unit tested using the helper classes and the Silverlight mocking framework ‘Moq’.
- [TestClass]
- public class DiggSearchServiceTest : SilverlightTest
- {
- [Asynchronous]
- [TestMethod]
- public void DiggSearchServiceSuccessTest()
- {
- DiggSearchService target = new DiggSearchService();
- StringBuilder sb = new StringBuilder();
- StringWriter sw = new StringWriter( sb );
- XDocument.Load( “/WrappedWebClient;component/Tests/stories.xml” ).Save( sw );
- var mock = new Mock<IWebDownloader>();
- mock.Setup( foo => foo.DownloadStringAsync( null ) ).Verifiable();
- target.WebDownloader = mock.Object;
- bool isLoaded = false;
- IEnumerable<DiggStory> result = null;
- target.SelectionCompleted += ( sender, e ) =>
- {
- isLoaded = true;
- //Get the results from the service
- result = e.EventData;
- };
- target.Run( “LSU” );
- EnqueueDelay( 1000 );
- //Mock the service request results
- EnqueueCallback( () => mock.Raise
- (
- bar => bar.DownloadStringCompleted += null,
- new WrappedDownloadStringCompletedEventArgs( sb.ToString(), null, false, null )
- ) );
- EnqueueConditional( () => isLoaded );
- EnqueueCallback( () => Assert.IsNotNull( result ) );
- EnqueueCallback( () => Assert.IsTrue( result.Count<DiggStory>() == 10 ) );
- EnqueueTestComplete();
- }
- }
Suggestions welcome…
leave a comment