Testing the Revised Compositional Update

In the last post I outlined a revised compositional Update method. I thought it would be interesting to share a test project that I used to validate this pattern and compare the results from the ‘old’ Update method and the new one.

Identifying the Test Cases

With compositional Updates, you have a handful of scenarios:

  • Parent can be: Modified or Unchanged.
  • Parent count: Must be 1.
  • Child can be: Modified or Unchanged or Deleted or Added.
  • Child count: 0 or more.

Even with this fairly constrained set of scenarios, your test case count can quickly become overwhelming. Because there is code in the Update method that touches just about all of these, I wanted something that got me good breadth, but was still manageable. I came up with these:

  • Tests with Parents Modified or Unchanged (all below tests multiplied by 2 to account for Parents in different states).
  • Tests with 0 and 3 Children in each of the Modified or Deleted or Added states. Test count: 6 x 2 = 12.
  • Test with Modified Parent and Unchanged Children. Test count = 1.
  • Tests with 3 modified Children, with a mix of 1 each of Modified, Deleted or Added states. Test count: 3 x 2 = 6.
  • Tests with 9 modified Children, with a mix of 3 each of Modified, Deleted or Added states. Test count: 3 x 2 = 6.

You can obviously go even crazier with the mix of Child actions, but I wanted to keep this as simple as possible while still hitting the most common scenarios. My total test count is 25, and I’m going to run them against the new and old Update methods, giving me a total of 50 tests.

Creating the Test Solution

For these tests, I’ll be using the Silverlight Unit Test Framework found in the April 2010 release of the Silverlight 4 Toolkit. After that’s installed, simply create a new Silverlight Unit Test Application. When prompted, you want to host the new test application in a Web site, and enable WCF RIA Services.

Silverlight Unit Test Application in the New Project Dialog

Setting up the Database

I knew that I eventually wanted to make this a blog post, so I wanted to keep the solution as distributable as possible. That meant no dependency on our test SQL servers. Instead, I decided to use a SQL Express database. I kept the schema very simple:

Simple Compositional Schema

I created a new Entity Framework model against my database, accepting all of the defaults, and my data access was almost ready to use.

Creating the DomainServices

To write these tests, we’ll want to create two DomainServices that use the same Entity Framework entities. These two DomainServices will be identical other than their Update methods: One will use the pattern from the MSDN topic and one will use the pattern from my previous blog post. Note that the ability to share Entities between DomainServices was added in WCF RIA Services SP1.

To do this, add two new DomainService classes with unique names (like OldPatternService and NewPatternService). Since we’ll be using composition, you only need to select the Parent entity, and be sure to generate associated metadata classes.

image

Once the DomainServices are created, open the generated *.metadata.cs file and add Include and Composition attributes to the Parent Entity’s Children property.

[MetadataTypeAttribute(typeof(Parent.ParentMetadata))]
public partial class Parent  
{ 
    internal sealed class ParentMetadata
    { 
        private ParentMetadata() { } 

        [Include] 
        [Composition] 
        public EntityCollection<Child> Children { get; set; } 
    } 
}

Now we need to update the two DomainServices so that they include the Children when the GetParents query is executed. To do this, simply use Entity Framework’s Include method:

public IQueryable<Parent> GetParents()  
{ 
    return this.ObjectContext.Parents.Include("Children");
}

The (almost) final piece of the DomainService logic is replacing the UpdateParent() method in both DomainServices with their corresponding patterns.

Creating a new Database for Each Test

After entering some test data into my database, I was faced with another issue. Since I’ll be modifying, deleting, and adding data through my tests, I want to start with a fresh database for each test.

In our internal test system, we do this with some stored procedures that create a test database on-demand, return the new connection string, then remove the database on-demand when the test is over. This ensures that the modified data from one test doesn’t affect other tests.

I quickly looked for ways to recreate this script to target SQL Express but wasn’t able to come up with anything that worked in a short amount of time (if anyone has tips, I’d love to hear them). Instead, I decided that copying my ‘base’ database file into a GUID-named subfolder before each test would address this requirement. Cleaning up these databases is as simple as deleting all the subfolders.

I should note that I don’t think this is a very robust way to handle this. I’m no SQL Express expert, but it seems like these files (or connections) get loaded into memory when you use them, meaning you can’t actually delete them until you run the tests again (you’ll see what I mean in the code below). I can also see my memory usage slowly climbing as each new database is created and used. But because these are throw-away tests for demonstration purposes, I thought this would be okay for this usage.

I decided to handle this database creation and deletion in a separate Helper DomainService that my tests could call on startup. This HelperService contains one Invoke method called CreateDatabase(), which attempts to delete any previous temporary databases, then creates a new temporary database itself, and sets a static property in another Helper class that can be used by our test DomainServices when they need to access the database. Here’s what the HelperService looks like:

[EnableClientAccess()]
public class HelperService : DomainService  
{
    [Invoke]
    public void CreateDatabase()
    {
        string app_data = HttpContext.Current.Server.MapPath("~/App_Data/");

        // Try to clean up any existing test databases. We can’t do it 
        // at the end of the previous test b/c the MDF is loaded 
        // and can’t be deleted. 
        foreach (string dir in Directory.GetDirectories(app_data))
        {
            try 
            {
                Directory.Delete(dir, true);
            }
            catch (Exception) { }
        }

        string origMdfPathAndFile = Path.Combine(app_data, "Composition.mdf");
        string newMdfName = "Composition.mdf";
        string guid = Guid.NewGuid().ToString();
        string newMdfPath = Path.Combine(app_data, guid);

        Directory.CreateDirectory(newMdfPath);
        File.Copy(origMdfPathAndFile, Path.Combine(newMdfPath, newMdfName), true);

        Helper.DatabaseGuid = guid;
    }
}

And here’s the Helper static class that stores the DatabaseGuid:

public static class Helper  
{
    public static string DatabaseGuid;
}

Now that we have a way to create temporary databases and store the Guid path names, we need a way to change the EF connection strings to point to the new database before any DomainService methods are invoked. The best way to do that is to override the DomainService CreateObjectConext method. Note that the long connection string was generated by EF and placed in my Web.config file by default. I’m simply adding the Guid-named subfolder to point to the newest temporary database.

protected override CompositionEntities CreateObjectContext()  
{
    CompositionEntities context = null;
    string connection = Helper.DatabaseGuid;
    if (!string.IsNullOrEmpty(connection))
    {
        connection = string.Format(@"metadata=res://*/CompositionModel.csdl|" +
            @"res://*/CompositionModel.ssdl|res://*/CompositionModel.msl;" +
            @"provider=System.Data.SqlClient;provider connection string=" +
            @"’data source=.\SQLEXPRESS;attachdbfilename=|DataDirectory|\{0}\"+ 
            @"Composition.mdf;integrated security=True;user instance=True;" + 
            @"multipleactiveresultsets=True;App=EntityFramework’", connection);

        context = new CompositionEntities(connection);
    }
    else 
    { 
        context = null;
    } 

    return context;
}

Creating Some Client-Side Plumbing

We’re finally ready to look at the client portion of our little project. But, as usual, there are a couple more things to consider before we get started writing our tests.

First, due to the nature of RIA Services, all of these tests are going to be asynchronous, which means our test class needs to derive from SilverlightTest. This allows us to take advantage of the Asynchronous attribute and the Enqueue* methods.

public class CompositionTests : SilverlightTest  

Second, I stated that I want to run the exact same scenarios against both of my DomainServices. This screams for the tests to run against an interface. So, I pulled out the DomainContext methods that I’ll be accessing in my tests into an ICompositionTestContext interface and declare that my two DomainContexts implement this interface:

public interface ICompositionTestContext  
{ 
    EntitySet<Parent> Parents { get; } 

    EntityQuery<Parent> GetParentsQuery();  

    LoadOperation<TEntity> Load<TEntity>(EntityQuery<TEntity> query, 
     Action<LoadOperation<TEntity>> callback, object userState) 
        where TEntity : Entity;  

    SubmitOperation SubmitChanges(Action<SubmitOperation> callback,  
     object userState);  
} 

public partial class NewPatternContext : ICompositionTestContext { } 

public partial class OldPatternContext : ICompositionTestContext { }  

Now that I’ve got an interface to work with, I can turn my CompositionTests class into an abstract base class that I can derive two other classes from to run my tests.

public abstract class CompositionTests : SilverlightTest  
{ 
    protected abstract ICompositionTestContext GetContext(); 
} 

[TestClass] 
public class Using_New_Pattern : CompositionTests  
{ 
    protected override ICompositionTestContext GetContext() 
    { 
        return new NewPatternContext();  
    } 
} 

[TestClass] 
public class Using_Old_Pattern : CompositionTests  
{  
    protected override ICompositionTestContext GetContext()  
    {   
        return new OldPatternContext();     
    } 
}

Any tests that I add to CompositionTests will be picked up and run by my other two test classes.

Next, I need to take advantage of that HelperService that I wrote to create my temporary databases. I do that by adding a TestInitialize() method to the CompositionTests class that will be run before every test, ensuring that each test gets its own database.

[TestInitialize]
[Asynchronous]
public void TestInitialize()  
{
    HelperContext context = new HelperContext();
    context.CreateDatabase((io) =>
        {
            EnqueueTestComplete();
        }, 
        null);
}        

Writing and Running the Tests

Now that most of my infrastructure is set up, it’s time to focus on the tests themselves. But wait, first I’ll need to build some more infrastructure for the tests themselves.

For example, I still had to write:
CompBlogPost_CroppedTestResults border="0" />
- A class to contain the changes that a test is making, so that I can verify the results. - A set of verification methods that can be run after each action. - Some common code to load the data and verify there were no load errors.

But in the interest of time, I’m going to wave my hands and skip right to the results (don’t worry, I’ve got a link to the entire solution at the end of the post). After writing all the infrastructure and the tests that I outlined above, I went ahead and kicked off a quick run.

The results looked exactly as I had hoped: All of the tests using the new pattern passed, and you can see that the tests run against the old pattern with multiple inserted Children failed. Armed with this knowledge, I felt comfortable saying that the new pattern worked as expected.

Even though this looks like quite an undertaking, once you go through these testing patterns a couple of times, you start to get better and better at it. In this case, I didn’t feel comfortable saying that the new pattern I was proposing would work in all cases unless I actually tried it. After a few hours of work, I feel much more comfortable making that statement.

Download the Solution

Grab the entire EFCompositionTests solution from here:

This solution requires: