Partager via


A General-Purpose Fake ADO.NET Data Service

In my previous post, I discussed how to implement a Fake ADO.NET Data Service for use with unit testing, showing how you can develop a one-off service that addresses specific needs, such as querying parents and children in the example.

As I hinted in that post, it's possible to create a reusable Fake ADO.NET Data Service that takes care of implementing IUpdatable based on in-memory data storage. To save you the trouble of doing that, I've created one that you can use, if you should so desire.

Following the standard ADO.NET Data Service coding idiom, the Fake ADO.NET Data Service consists of two classes:

  • FakeDataService<T>, which derives from DataService<T>. This is simply the counterpart of MyDataService from the previous example. You can use this class as is.
  • DataContainer, which implements IUpdatable. Although I haven't made this class abstract, it only becomes useful when you derive from it and define your own queries.

Let's say that I want to use these two classes to unit test the same service client as in the previous example. Instead of MyService (where I manually had to implement IUpdatable), I can now define my data container as simply as this:

 public partial class ParentChildContainer : DataContainer
 {
     public IQueryable<Child> Children
     {
         get 
         {
             return this.GetStore<Child>().AsQueryable(); 
         }
     }
  
     public IQueryable<Parent> Parents
     {
         get
         {
             return this.GetStore<Parent>().AsQueryable(); 
         }
     }
 }

The only thing you have to do is just derive from DataContainer and specify your queries.

Although you don't have to use FakeDataService<T> to host your Fake DataContainer, it makes it easy to set up the Fake service in a unit test:

 [TestMethod]
 public void TestCanSetupFakeService()
 {
     // Fixture setup
     Uri address = new Uri("https://localhost/MyDataService");
     FakeDataService<ParentChildContainer> service = 
         new FakeDataService<ParentChildContainer>(
             new ParentChildContainer());
     using (WebServiceHost host =
         new WebServiceHost(service, new[] { address }))
     {
         host.Open();
         // Exercise system
         // ...
         // Verify outcome
         // ...
         // Teardown
     }
 }

How does this work? DataContainer stores each 'table' in a List<T>, and each list is again stored in a dictionary that maps the list to the type. Here's the part of DataContainer that doesn't contain the IUpdatable implementation:

 public partial class DataContainer
 {
     private readonly Dictionary<Type, IList> stores;
  
     public DataContainer()
     {
         this.stores = new Dictionary<Type, IList>();
     }
  
     public IList<T> GetStore<T>()
     {
         if (!this.stores.ContainsKey(typeof(T)))
         {
             this.AddStore<T>();
         }
         return (IList<T>)this.stores[typeof(T)];
     }
  
     private void AddStore<T>()
     {
         this.stores.Add(typeof(T), new List<T>());
     }
 }

When you call GetStore<T>, DataContainer implicitly creates the list for the give type if it doesn't already exist.

The rest of DataContainer deals with implementing IUpdatable:

 #region IUpdatable Members
  
 public void AddReferenceToCollection(object targetResource,
     string propertyName, object resourceToBeAdded)
 {
     IList list = (IList)targetResource.GetType().
         GetProperty(propertyName).
         GetValue(targetResource, null);
     list.Add(resourceToBeAdded);
 }
  
 public void ClearChanges()
 {
 }
  
 public object CreateResource(string containerName,
     string fullTypeName)
 {
     Type t = this.ResolveType(fullTypeName);
     object resource = Activator.CreateInstance(t);
     this.stores[t].Add(resource);
     return resource;
 }
  
 public void DeleteResource(object targetResource)
 {
     foreach (IList list in this.stores.Values)
     {
         list.Remove(targetResource);
     }
 }
  
 public object GetResource(IQueryable query,
     string fullTypeName)
 {
     return query.Cast<object>().AsEnumerable().
         FirstOrDefault();
 }
  
 public object GetValue(object targetResource,
     string propertyName)
 {
     return targetResource.GetType().
         GetProperty(propertyName).
         GetValue(targetResource, null);
 }
  
 public void RemoveReferenceFromCollection(
     object targetResource,
     string propertyName,
     object resourceToBeRemoved)
 {
     IList list = (IList)targetResource.GetType().
         GetProperty(propertyName).
         GetValue(targetResource, null);
     list.Remove(resourceToBeRemoved);
 }
  
 public object ResetResource(object resource)
 {
     return resource;
 }
  
 public object ResolveResource(object resource)
 {
     return resource;
 }
  
 public void SaveChanges()
 {
 }
  
 public void SetReference(object targetResource,
     string propertyName, object propertyValue)
 {
     targetResource.GetType().GetProperty(propertyName).
         SetValue(targetResource, propertyValue, null);
 }
  
 public void SetValue(object targetResource, 
     string propertyName, object propertyValue)
 {
     targetResource.GetType().GetProperty(propertyName).
         SetValue(targetResource, propertyValue, null);
 }
  
 #endregion
  
 private Type ResolveType(string fullTypeName)
 {
     return (from t in this.stores.Keys
             where t.FullName == fullTypeName
             select t).First();
 }

If you find this useful, I've attached the code to this post for your downloading pleasure. As always, the standard disclaimers apply.

DataServicesQualityTools.zip

Comments