Freigeben über


Creating A Fake ADO.NET Data Service

Previously, I discussed unit testing ADO.NET Data Service clients using a Fake ADO.NET Data Service, and I promised to demonstrate how to create such a service. In this article I will continue the previous example and implement the Fake MyService class.

The basics of MyService is pretty simple:

 public partial class MyService
 {
     public MyService()
     {
         this.ParentStore = new List<Parent>();
         this.ChildStore = new List<Child>();
     }
  
     public IQueryable<Child> Children
     {
         get { return this.ChildStore.AsQueryable(); }
     }
  
     public IQueryable<Parent> Parents
     {
         get { return this.ParentStore.AsQueryable(); }
     }
  
     public IList<Child> ChildStore { get; private set; }
  
     public IList<Parent> ParentStore { get; private set; }
 }

This service supports two 'tables': Parents and Children (Parents can have Children). Since this is a Fake, it only needs to store its data in memory, so I use two List<T> instances for that. To provide direct access to the service's data, MyService exposes both lists as public properties (you can make those properties internal if your Fake service is defined in the same assembly as your tests).

This is all you need if you only need to support read-only scenarios. If the client you want to test only queries its service, you don't need to do anything else. On the other hand, if you also need to test Create, Update and Delete (CUD) operations, you will need to implement IUpdatable:

 public partial class MyService : IUpdatable

Implementing this interface enables you to support CUD operations. Since it contains twelve members, I'm not going to walk you through each and every one of them, but rather present some highlights. If you are interested in more details on the methods of this interface, I'll recommend this post on the Project Astoria Team Blog.

To support each of the simple CUD operations, multiple methods must be implemented, although there's an overlap.

To create a new item, the first method to implement is CreateResource.

 public object CreateResource(string containerName, 
     string fullTypeName)
 {
     switch (containerName)
     {
         case "Parents":
             Parent p = new Parent();
             this.ParentStore.Add(p);
             return p;
         case "Children":
             Child c = new Child();
             this.ChildStore.Add(c);
             return c;
         default:
             throw new ArgumentException(
                 "Unknown container name.");
     }
 }

This implementation simply creates a new instance of the requested item type and adds it to the in-memory list. Here, I was just being a bit lazy and simply switched on the containerName, but it's perfectly possible to create a more generic implementation using a bit of Reflection, since the fullTypeName parameter specifies which type of object to create.

The CreateResource method's responsibility is to create a default instance of the requested type and add it to the underlying data store (in this case just an in-memory list). However, it doesn't assign values to the item's properties, since this is the duty of the SetValue method.

 public void SetValue(object targetResource, 
     string propertyName, object propertyValue)
 {
     Type t = targetResource.GetType();
     t.GetProperty(propertyName).SetValue(
         targetResource, propertyValue, null);
 }

At this point, I found it easier to implement the method using Reflection than explicitly switching on both the targetResource and propertyName parameters.

The last method that must be implemented to support the Create scenario is ResolveResource.

 public object ResolveResource(object resource)
 {
     return resource;
 }

Since the Fake service is already working with in-memory objects, it can just return the object itself.

With these methods implemented, updating an item is not quite as involved, since it reuses the SetValue and ResolveResource methods, so I only need to implement the GetResource method.

 public object GetResource(IQueryable query,
     string fullTypeName)
 {
     return query.Cast<object>().AsEnumerable().
         FirstOrDefault();
 }

This implementation requires a bit of explanation. The query argument that the method receives is actually an expression over the service's exposed queries (i.e. Parents and Children). Since the service operates on in-memory objects, it doesn't need to transform the expression, but can simply evaluate it directly, which is what the AsEnumerable extension method does. However, IQueryable doesn't have an AsEnumerable extension method, whereas IQueryable<T> does - hence the cast to object.

Deleting an item reuses GetResource and ResolveResource, so to support this scenario, the DeleteResource method is the only extra method that must be implemented.

 public void DeleteResource(object targetResource)
 {
     ((IList)this.ParentStore).Remove(targetResource);
     ((IList)this.ChildStore).Remove(targetResource);
 }

Once again, I decided to keep things simple and simply hard-code knowledge of ParentStore and ChildStore into the method, but it would be easy to generalize this approach.

The rest of the methods of IUpdatable address more advanced scenarios concerning references between items, and I'm not going to cover them in this post, since it's already becoming quite long. If you are interested, I've provided the entire sample code as an attachment to this post, so download and peruse it at your leisure (obviously, the usual disclaimers apply).

Update: I've now posted a general-purpose Fake ADO.NET Data Service that should hopefully address most of your needs.

DataServiceTesting.zip

Comments

  • Anonymous
    January 19, 2009
    In my previous post , I discussed how to implement a Fake ADO.NET Data Service for use with unit testing,