VS2012, EF, and Mocking

A couple of years ago I presented at TriNug’s code camp a way to build an interface for EF 1.0 using partial classes.  I thought I would revisit that with VS2012 to see if it is still possible using EF 5.0 and the mocking framework in VS2012.

My first stop was to  install SQL Server Express and then Run Northwind.exe.  Note that you can’t just attach to the Northwind .mdf because of version incompatibilities.  Rather, you need to open SQL Server Express and run the scripts that came with the install.  In any event, once it is installed, it was straight forward to add an EF representation to the project.  The one cool thing that I noticed is that you can change the color of different tables via the property panel:

image

Sure enough, by changing the fill color, you get a really cool effect like this:

image

With EF added to the project, coded up a factory like this:

public List<Territory> GetTerritoriesForARegionId(Int32 regionId)
{
    List<Territory> territories = null;

    if (regionId < 0)
    {
        throw new ArgumentOutOfRangeException("regionId cannot be less than 0");
    }

    using (NorthwindEntities entities = new NorthwindEntities())
    {
        territories = entities.Territories.Where(t => t.RegionID == regionId).ToList();
    }

    return territories;
}

I then went to right click –> add Tests like you do in VS2010 and… Oh No!  That feature is not in VS2012

I then went to hand-code a unit test project, class, and method like this:

[TestMethod]
public void GetTerritoryForARegionIdWithRegionIdOne_ReturnsThreeRecords()
{
    TerritoryFactory factory = new TerritoryFactory();
    List<Territory> territories = factory.GetTerritoriesForARegionId(1);

    Int32 expected = 3;
    Int32 actual = territories.Count;

    Assert.AreEqual(expected, actual);
}

When I ran it, I got this:

image

I needed to copy/paste the .config over.  (Which is the 1st clue that my test is is actually an integration test).  I then ran the test and got red:

image

So there are 19 records in the database.  I can do a couple of things here.  I could check that any value is coming out of the database:

[TestMethod]
public void GetTerritoryForARegionIdWithRegionIdOne_ReturnsThreeRecords()
{
    TerritoryFactory factory = new TerritoryFactory();
    List<Territory> territories = factory.GetTerritoriesForARegionId(1);
    Assert.IsNotNull(territories);

}

The problem is that the working code will always return a instance – unless an exception is thrown. I suppose I can leave the list as null and put a try…catch around the lambda, but that doesn’t really get you anything.  So really, what do you need to test? It is the lambda expression in the working code:

territories = entities.Territories.Where(t => t.RegionID == regionId).ToList();

The problem is that the working code (and therefore the test) is dependent on the entity framework. Breaking the expression into 2 parts, I don’t need to test if the EF does its job, I only need to test my code:

t => t.RegionID == regionId

So how to I make sure you are writing the correct statement to pull down the results you expect? I need to isolate the statement and following functional programming, guarantee that the result will be the same.

To that end, I need to remove the dependency on volatile data. So no database. So how do you make a EF that is not database specific? You sub it out. There are a couple of options (xUnit Test Patterns p.171) . The way I favor is to first inject that context into any class that is using it (and I prefer property injection over constructor injection). 

Note that the dependent class no longer controls the lifetime of the context, so no “using” statement in it.

public class TerritoryFactory
{
    public NorthwindEntities NorthwindEntities { get; set; }

    public List<Territory> GetTerritoriesForARegionId(Int32 regionId)
    {
        List<Territory> territories = null;

        if (regionId < 0)
        {
            throw new ArgumentOutOfRangeException("regionId cannot be less than 0");
        }

        territories = this.NorthwindEntities.Territories.Where(t => t.RegionID == regionId).ToList();

        return territories;
    }
}

Once I am injecting the context, I can pass in a local instance of an EF Context. A If I want to sub out a different version, you need to implement an interface.

The problem is that EF is auto generated – so you can’t just make an interface and have it implement it.

image

This is the problem I solved at the code camp 2 years ago (Note that this would be so much easier if MSFT added interfaces to their auto generated classes.  Just sayin’).  In any event, the way around it is to use a partial class and then extract the interface from that partial class.  To that end, I added a new class to the project with the following code:

public partial class NorthwindEntities 
{

}

I then used Refactor –> Extract Interface and I got an interface:

interface INorthwindEntities
{
System.Data.Entity.DbSet<Category> Categories { get; set; }
System.Data.Entity.DbSet<CustomerDemographic> CustomerDemographics { get; set; }
System.Data.Entity.DbSet<Customer> Customers { get; set; }
System.Data.Entity.DbSet<Employee> Employees { get; set; }
System.Data.Entity.DbSet<Order_Detail> Order_Details { get; set; }
System.Data.Entity.DbSet<Order> Orders { get; set; }
System.Data.Entity.DbSet<Product> Products { get; set; }
System.Data.Entity.DbSet<Region> Regions { get; set; }
System.Data.Entity.DbSet<Shipper> Shippers { get; set; }
System.Data.Entity.DbSet<Supplier> Suppliers { get; set; }
System.Data.Entity.DbSet<Territory> Territories { get; set; }
}

I then changed my unit test code to use the interface like so:

[TestMethod]
public void GetTerritoryForARegionIdWithRegionIdOne_ReturnsThreeRecords()
{
    TerritoryFactory factory = new TerritoryFactory();
    INorthwindEntities entities = new NorthwindEntities();
    factory.NorthwindEntities = entities;
    List<Territory> territories = factory.GetTerritoriesForARegionId(1);

    Int32 expected = 3;
    Int32 actual = territories.Count;

    Assert.AreEqual(expected, actual);
}

When I ran the test, I got the same red (red>green>refactor?  how about red > redder > refactor).

image

So the last thing is the implementation.  How do we build a “fake” in memory instance of the Northwind Entities that returns 3 Territories?  Enter Mocking Framework!

image

 

And then switch out the implementation for the Mock:

[TestMethod]
public void GetTerritoryForARegionIdWithRegionIdOne_ReturnsThreeRecords()
{
    TerritoryFactory factory = new TerritoryFactory();

    //Replace the actual implementation with the Fake
    //INorthwindEntities entities = new Tff.NorthwindApp.NorthwindEntities();
    INorthwindEntities entities = new Fakes.StubNorthwindEntities();

    //Add in Stub Data
    entities.Territories.Add(new Territory { TerritoryID = "1", RegionID = 1 });
    entities.Territories.Add(new Territory { TerritoryID = "2", RegionID = 1 });
    entities.Territories.Add(new Territory { TerritoryID = "3", RegionID = 1 });

    factory.NorthwindEntities = entities;
    List<Territory> territories = factory.GetTerritoriesForARegionId(1);
    Int32 expected = 3;
    Int32 actual = territories.Count;

    Assert.AreEqual(expected, actual);

}

I half expected that running this would get green.  I assumed that the default constructor of EF (which is what the Mocking framework would use) would just give an empty graph without going to the database.  Alas, I was wrong:

image

So the Stub is still going out to the database.

clip_image001

I then spent a couple of hours of trying to get around this “feature” of the EF being so tightly coupled to the database and I gave up.  I then tried the same technique with Linq to Sql.  Unfortunately, I ran into the same problem.  I then went to stack overflow and got nothing.

To me, this is a real limitation of EF.  EF should be able to be stubbed easily.  MSFT missed a real opportunity here…

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: