Unit Testing ADO.NET Data Service Clients

In my previous post, I discussed unit testing ADO.NET Data Services and how you can host and test the service itself. In this post, I'll take a look at the opposite scenario: You have a client of an ADO.NET Data Service, and you want to unit test the client without relying on the real, production service.

In other words, you want to test the real client against a Test Double service. Presently, I'll show you how to create and host a Fake ADO.NET Data Service that the client code can invoke. Obviously, the client in this example uses the ADO.NET Data Services Client API, so I'll demonstrate how you can use that to invoke the fake service from within the same test.

Before we dive into the specifics of the implementation, here's a simple test that verifies that the client can retrieve all Parent instances from the service:

image

As you can see from the figure, the test combines two logical tiers into one test: The service and the client.

The service tier is simply part of the test's Fixture, and while I'm not concerned with testing it in this context, I still need to set it up. This part uses the ADO.NET Data Services API to define and host the service.

The client part uses the ADO.NET Data Services Client API to query the service via its REST interface and verify the result.

Notice that I use an instance of MyDataService to configure the host. While not strictly necessary in this test, when hosting a Fake service, it's desirable to have a reference to the fake instance itself, since that allows you to configure and query it through its Back Door. Here's a test that uses the MyDataService instance to verify that a Parent instance was deleted from the service:

 [TestMethod]
 public void ClientCanDeleteInstance()
 {
     // Fixture setup
     Uri address = new Uri("https://localhost/MyDataService");
     MyDataService service = new MyDataService();
     using (WebServiceHost host = 
         new WebServiceHost(service, new[] { address }))
     {
         host.Open();
  
         MyDataServiceContext ctx =
             new MyDataServiceContext(address);
         var victim = (from p in ctx.Parents
                       where p.Id == 3
                       select p).Single();
         // Exercise system
         ctx.DeleteObject(victim);
         ctx.SaveChanges();
         // Verify outcome
         Assert.IsFalse(service.Data.Parents.Any(
             p => p.Id == victim.Id), "Deleted");
         // Teardown
     }
 }

As in the previous example, all Fixture Setup before the declaration of the MyDataServiceContext instance is part of the logical service tier, while the remaining Fixture Setup code, as well as the execution of the SUT is part of the logical client tier. However, notice that the verification now takes place on the logical service tier, since I'm using the fake service instance as a Back Door, instead of relying on the REST interface to query the service.

Since DataServiceHost doesn't include a constructor that takes a single instance as a parameter, I use its base class WebServiceHost instead. Reflector shows that DataServiceHost really adds no behavior to its base class, so using WebServiceHost as a host should be safe - for now (let's hope it stays that way, or even better, that it gets a constructor overload that accepts a service instance).

In normal cases, I consider it best practice to explicitly load data into the fake service as part of the Fixture Setup phase, but to keep these examples simple, I decided to implicitly load some test data as part of creating the MyDataService instance. That is why, in the above example, I can successfully retrieve a Parent instance with an Id of 3. Please be aware that this is not a strategy I endorse - I only did it to keep the example a bit less complex.

Here's the MyDataService class in its entirety:

 [ServiceBehavior(IncludeExceptionDetailInFaults = true, 
     InstanceContextMode = InstanceContextMode.Single)]
 public class MyDataService : DataService<MyService>
 {
     private readonly MyService service_;
  
     public MyDataService()
     {
         this.service_ = new MyService();
  
         IList<Parent> ps = this.service_.ParentStore;
         ps.Add(new Parent() { Id = 1, Text = "Ploeh" });
         ps.Add(new Parent() { Id = 2, Text = "Fnaah" });
         ps.Add(new Parent() { Id = 3, Text = "Ndøh" });
         ps.Add(new Parent() { Id = 4, Text = "Foo" });
         ps.Add(new Parent() { Id = 5, Text = "Bar" });
  
         IList<Child> cs = this.service_.ChildStore;
         cs.Add(new Child() { Id = 1, Text = "Child 1" });
         cs.Add(new Child() { Id = 2, Text = "Child 2" });
         cs.Add(new Child() { Id = 3, Text = "Child 3" });
     }
  
     public MyService Data
     {
         get { return this.service_; }
     }
  
     public static void InitializeService(
         IDataServiceConfiguration config)
     {
         config.SetEntitySetAccessRule("*",
             EntitySetRights.All);
         config.UseVerboseErrors = true;
     }
  
     protected override MyService CreateDataSource()
     {
         return this.service_;
     }
 }

As I described, I use the constructor to set up some implicit test data. While I want to reiterate that this is not something I would recommend, I chose to include this code to make it clear what I was talking about.

The service is decorated with the ServiceBehavior attribute, which sets its InstanceContextMode to Single to allow it to be hosted as a Singleton service by WebServiceHost. This, again, is what allows me to query the fake service's data via its Back Door.

Since MyService simply stores data in memory, MyDataService needs to keep the same instance around for each request, so it overrides CreateDataSource to ensure that the same data container instance is always being referenced for the lifetime of the instance.

In a later post, I will describe how to create the fake data service, MyService.