Tagged: Testability

Creating Testable WCF Data Services

One of the effective ways to expose your data on the web is to use WCF Data Services and OData protocol.

In its simplest form, a WCF Data Service can be built by exposing your ADO.NET Entity Framework ObjectContext through the DataService<T> class.
However, when it comes to testing, especially in situations when you have to enhance data in the OData Feed with some additional information (through WCF Behaviors for example), or when you have to verify that your Query Interceptors work as designed, using Object Context that is bound to a live database is hard because:

  • Your tests that have little to do with the shape of your data become dependent on the database, which is a big problem
  • You have maintain test data in the database when you run your tests, which is a maintenance overhead

So there lies our problem: how to expose data using WCF Data Services in a way where the database dependency can be mocked out during unit testing? The short answer is that we need to achieve better testability by introducing persistence-ignorant domain layer and by making our existing Object Context implement an interface that can be mocked/substituted in testing.

This article describes the concrete incremental steps you can take to get you from a standard Data Service built on top of Entity Framework Object Context to a solution that will satisfy testability requirements above. I will demonstrate the technique on a sample service connected to the AdventureWorksLT database.

Initial State: Data Service Of Object Context

public class MyDataService : DataService
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("Products", EntitySetRights.All);
        config.SetEntitySetAccessRule("ProductCategories", EntitySetRights.All);
        config.SetEntitySetAccessRule("ProductDescriptions", EntitySetRights.All);
        config.SetEntitySetAccessRule("ProductModelProductDescriptions", EntitySetRights.All);
        config.SetEntitySetAccessRule("ProductModels", EntitySetRights.All);
        config.SetEntitySetAccessRule("SalesOrderHeaders", EntitySetRights.All);
        config.SetEntitySetAccessRule("SalesOrderDetails", EntitySetRights.All);

        config.UseVerboseErrors = true;
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }
}

Step 1: Create an Interface that represents you Data Repository

This step is easy: For every entity type you expose in your Data Service, add a method that returns IQueryable<T>:

namespace MyDataService.DataAccess
{
    public interface IMyDataRepository
    {
        IQueryable</pre>
<address>Addresses { get; }
 IQueryable Customers { get; }
 IQueryable CustomerAddresses { get; }
 IQueryable Products { get; }
 IQueryable ProductCategories { get; }
 IQueryable ProductDescriptions { get; }
 IQueryable ProductModelProductDescriptions { get; }
 IQueryable ProductModels { get; }
 IQueryable SalesOrderHeaders { get; }
 IQueryable SalesOrderDetails { get; }
 }
}

After interface has been created, you can provide default/production implementation in the hand-written partial class that corresponds to your Object Context by just returning Object Sets already defined by you Object Context:

namespace MyDataService.DataAccess
{
    public partial class AdventureWorksLTEntities: IMyDataRepository
    {
        IQueryable IMyDataRepository.Products
        { get { return this.Products; } }

        IQueryable IMyDataRepository.ProductCategories
        { get { return this.ProductCategories; } }

        IQueryable IMyDataRepository.ProductDescriptions
        { get { return this.ProductDescriptions; } }

        IQueryable IMyDataRepository.ProductModelProductDescriptions
        { get { return this.ProductModelProductDescriptions; } }

        IQueryable IMyDataRepository.ProductModels
        { get { return this.ProductModels; } }

        IQueryable IMyDataRepository.SalesOrderHeaders
        { get { return this.SalesOrderHeaders; } }

        IQueryable IMyDataRepository.SalesOrderDetails
        { get { return this.SalesOrderDetails; } }

        IQueryable</pre>
<address>IMyDataRepository.Addresses
 { get { return this.Addresses; } }

 IQueryable IMyDataRepository.Customers
 { get { return this.Customers; } }

 IQueryable IMyDataRepository.CustomerAddresses
 { get { return this.CustomerAddresses; } }
 }
}

Now you have an interface in your system that defines data access methods already implemented by your Object Context.
This means that if you build the rest of the system to rely/depend on this interface, rather than use its implementation (i.e. the Object Context), you can achieve better flexibility and the testability without having to worry about test databases and policing test data in your build environment and on developers’ workstations.

This is a worthy and very achievable goal, but we have a few steps remaining to fully get us there.

Step 2: Change Code Generation Template to use Self-Tracking Entities

This step is needed because you typically do not want you entities to be database-bound, and instead you want a reusable, independent domain layer that can be used in multiple scenarios. This move will contribute to a better code flexibility and layering without jeopardizing data access functionality that Entity Framework provides out of the box. Full discussion on why separate persistence-ignorant domain layer is a good thing is out of scope for this article. If you want to research the subject further, I would start herehere and here.

Adding Self-Tracking Entities Code Generation

Having done that, your Project containing Data Context will look differently:

  • You will see AdventureWorks.Context.tt and code-generated Object Context in the AdventureWorks.Context.cs and AdventureWorks.Context.Extensions.cs
  • You will see AdventureWorks.tt and code-generated entity classes that match you Entity Framework Model.

Note that entity classes do not have any dependency on the EntityObject and therefore do not have to live in your Data Access layer.

Self-Tracking Entities: Generated Code

Step 3: Move Your POCO Entities into a separate “Domain” assembly

Now that we have a domain layer of self-tracking plain old CLR objects (POCOs), we want to make it reusable.
This means separating the entity classes into a separate assembly that does not have any dependencies on Entity Framework, System.Data or any other persistence technologies. The tricky part is that we want to still get the benefits of EF designer experience, i.e. if we added, removed or modified some entities, we want them to be re-generated the usual way.

To achieve that, follow these steps:

  • Create a new Project where you intend to host your Domain Layer. Reference System.Runtime.Serialization.
  • Right-Click on the Entity Code Generation template (AdventureWorks.tt) and select Exclude From Project.
  • In your Domain Layer project, add existing Entity Code Generation Template (located in EDMX folder) as a link.Visual Studio will regenerate Domain Classes in the correct namespace that matches your Domain Layer:Seperate Domain Layer
  • Reference your new Domain project from Data Layer and from the Web Project hosting your Data Service.
  • Add using statements for your Domain namespace to fix compiler errors.
  • You will notice that one of the places you had to add using statement is you code-generated AdventureWorks.Context.cs. Since this class is being generated every time, it is better to add additional using statement to the .Context.tt template:
    EntityContainer container = ItemCollection.GetItems().FirstOrDefault();
    if (container == null)
    {
        return "// No EntityContainer exists in the model, so no code was generated";
    }
    
    WriteHeader(fileManager, "MyDataService.Domain");
    BeginNamespace(namespaceName, code);
    

Step 4: Create your own Data Context that composes your Data Repository

In the Data Access project, add a class that will represent new context will use in the Data Service.
This class replaces Entity Context used before, and therefore it should implement all properties that return IQueryable instances for the Data Service. Since our Repository already implements all this, the implementation is trivial:

namespace MyDataService.DataAccess
{
    public class MyDataContext
    {
        public IMyDataRepository Repository { get; private set; }

        public MyDataContext(IMyDataRepository repository)
        {
            if (repository == null)
            {
                throw new ArgumentNullException("repository", "Expecting a valid Repository");
            }

            this.Repository = repository;
        }

        public MyDataContext(): this(new AdventureWorksLTEntities())
        {
        }

        public IQueryable</pre>
<address>Addresses
 {
 get { return this.Repository.Addresses; }
 }

 public IQueryable Customers
 {
 get { return this.Repository.Customers; }
 }

 public IQueryable CustomerAddresses
 {
 get { return this.Repository.CustomerAddresses; }
 }

 public IQueryable Products
 {
 get { return this.Repository.Products; }
 }

 public IQueryable SalesOrderDetails
 {
 get { return this.Repository.SalesOrderDetails; }
 }

 public IQueryable SalesOrderHeaders
 {
 get { return this.Repository.SalesOrderHeaders; }
 }

 public IQueryable ProductCategories
 {
 get { return this.Repository.ProductCategories; }
 }

 public IQueryable ProductDescriptions
 {
 get { return this.Repository.ProductDescriptions; }
 }

 public IQueryable ProductModelProductDescriptions
 {
 get { return this.Repository.ProductModelProductDescriptions; }
 }

 public IQueryable ProductModels
 {
 get { return this.Repository.ProductModels; }
 }
 }
}

Step 5: Update Data Service to use custom Data Context

[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class MyDataService : DataService
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("Products", EntitySetRights.All);
        config.SetEntitySetAccessRule("ProductCategories", EntitySetRights.All);
        config.SetEntitySetAccessRule("ProductDescriptions", EntitySetRights.All);
        config.SetEntitySetAccessRule("ProductModelProductDescriptions", EntitySetRights.All);
        config.SetEntitySetAccessRule("ProductModels", EntitySetRights.All);
        config.SetEntitySetAccessRule("SalesOrderHeaders", EntitySetRights.All);
        config.SetEntitySetAccessRule("SalesOrderDetails", EntitySetRights.All);

        config.UseVerboseErrors = true;
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }
}

One you do that, Data Services infrastructure will switch from Entity Framework provider to a Reflection Provider.
There are subtle but important differences between the two. One of the symptoms of those differences is that Reflection Provider does not understand POCO’s the same way Entity Provider understands Entities.
If you run you service as is, you will get a bunch of errors stating that there are some unsupported properties.
To fix those errors, you would have to create partial classes for your Domain entities that do two things:

  • Explicitly define data service keys
  • Exclude Change Tracker from list of properties available for the Data Services to pick up
[DataServiceKey("AddressID")]
[IgnoreProperties("ChangeTracker")]
public partial class Address
{
}

Finally, verify that your service still works by testing it in the browser.
You should be able to perform any operation you did before, but now you can also Unit Test your service without having to have a live database to talk to!

Step 6: For testing, inject Mock Data Repository

To do that, we will need to figure out how to “legally” let Data Service know that we want it to use our implementation of the Data Repository
instead of default one that connects to a real database using Entity Framework. One way to do that is to use Inversion of Control and Dependency Injection. However, making Data Service and WCF aware of IoC Containers is a tricky proposition – there is a simpler solution

What if we created a factory method and used it to create instances of the custom context whenever the Data Service needs one? Fortunately, WCF Data Service provides a way to do that by overriding CreateDataSource method in the Data Service class:

public static Func DataContextFactory;

protected override MyDataContext CreateDataSource()
{
    return DataContextFactory != null ? DataContextFactory() : base.CreateDataSource();
}

Now our test code can set the Factory to create Data Context already initialized with a mock Repository.

This is, however, is only part of the problem – we also want to be able to host the Data Service inside of the test process without having to deploy anything to a real web server.
Once again, this is solvable by creating in-process service host:

public static class TestServiceHost
{
    internal static DataServiceHost Host
    {
        get;
        set;
    }

    internal static Uri HostEndpointUri
    {
        get
        {
            return Host.Description.Endpoints[0].Address.Uri;
        }
    }

    ///
<summary> /// Initialize WCF testing by starting services in-process
 /// </summary>
    public static void StartServices()
    {
        var baseServiceAddress = new Uri("http://localhost:8888");
        Host = new DataServiceHost(typeof(Web.MyDataService), new[] { baseServiceAddress });
        Host.AddServiceEndpoint(typeof(IRequestHandler), new WebHttpBinding(), "Service.svc");
        Host.Open();
        Trace.WriteLine(String.Format(
            "Started In-Process Services Host on Base Address {0}, Endpoint Address {1}",
            baseServiceAddress,
            HostEndpointUri));
    }

    public static void StopServices()
    {
        if (Host != null)
        {
            Host.Close();
            Trace.WriteLine(String.Format("Stopped In-Process Services Host on {0}", HostEndpointUri));
        }
    }
}

Now we can start writing tests that do not need the hosting environment and a database to verify that your service works as designed, especially if you start adjusting its behavior to add security, custom metadata extensions, query interceptors,
exception handling, etc.:

[TestClass]
public class DataServiceTest
{
    [TestInitialize]
    public void SetUp()
    {
        TestServiceHost.StartServices();
    }

    [TestCleanup]
    public void TearDown()
    {
        TestServiceHost.StopServices();
    }

    [TestMethod]
    public void DataService_QueryProductss_ReturnsCorrectData()
    {
        var expected = new List()
                            {
                                new Product { Name = "Product A" },
                                new Product { Name = "Product B" }
                            };
        var mockRepository = new Mock();
        mockRepository.Setup(r => r.Products).Returns(expected.AsQueryable());
        Web.MyDataService.DataContextFactory = () => new MyDataContext(mockRepository.Object);

        var target = new ServiceClient.MyDataContext(TestServiceHost.Host.Description.Endpoints[0].Address.Uri);
        var actual = target.Products.ToList();
        Assert.AreEqual(expected.Count(), actual.Count());
        Assert.IsTrue(actual.All(p => expected.Any(x => x.Name == p.Name)));
    }
}

Summary

Building applications that expose WCF Data Service is relatively easy. What is more difficult is to make these applications testable.

This article demonstrated how to do that by switching code generation template to self-tracking POCOs,
introducing persistence-ignorant domain layer and by making our existing Object Context implement an interface that can be mocked/substituted in testing.
Every one of these improvements is for the better, but curiously, all of them happened in response to one goal to
make our system more testable.

Advertisements