Unit Testing with EF Code First DataContext

This is more a solution / work around than a real question. I post it here since I could not find this solution when the stack overflowed or even after a lot of Google.

Problem:

I have an MVC 3 webapp using EF 4 code that I want to write for unit tests. I also use NCrunch to run unit tests on the go, as I code, so I would like to avoid support in a real database here.

Other solutions:

IDataContext

I found this to be the most acceptable way to create a datacontext in memory. This effectively involves writing the IMyDataContext interface for your MyDataContext, and then using the interface in all of your controllers. An example of this is here .

This is the route I went from the very beginning, and even got to writing a T4 template to extract IMyDataContext from MyDataContext, since I don't like to support duplicate dependent code.

However, I quickly found that some Linq statements do not work when using IMyDataContext instead of MyDataContext. In particular, such requests raise a NotSupportedException

var siteList = from iSite in MyDataContext.Sites
               let iMaxPageImpression = (from iPage in MyDataContext.Pages where iSite.SiteId == iPage.SiteId select iPage.AvgMonthlyImpressions).Max()
               select new { Site = iSite, MaxImpressions = iMaxPageImpression };

My decision

It was pretty simple. I just subclassed MyInMemoryDataContext for MyDataContext and overrided all the IDbSet <..> properties, as shown below:

public class InMemoryDataContext : MyDataContext, IObjectContextAdapter
{
    /// <summary>Whether SaveChanges() was called on the DataContext</summary>
    public bool SaveChangesWasCalled { get; private set; }

    public InMemoryDataContext()
    {
        InitializeDataContextProperties();
        SaveChangesWasCalled = false;
    }

    /// <summary>
    /// Initialize all MyDataContext properties with appropriate container types
    /// </summary>
    private void InitializeDataContextProperties()
    {
        Type myType = GetType().BaseType; // We have to do this since private Property.Set methods are not accessible through GetType()

        // ** Initialize all IDbSet<T> properties with CollectionDbSet<T> instances
        var DbSets = myType.GetProperties().Where(x => x.PropertyType.IsGenericType && x.PropertyType.GetGenericTypeDefinition() == typeof(IDbSet<>)).ToList();
        foreach (var iDbSetProperty in DbSets)
        {
            var concreteCollectionType = typeof(CollectionDbSet<>).MakeGenericType(iDbSetProperty.PropertyType.GetGenericArguments());
            var collectionInstance = Activator.CreateInstance(concreteCollectionType);
            iDbSetProperty.SetValue(this, collectionInstance,null);
        }
    }

    ObjectContext IObjectContextAdapter.ObjectContext 
    {
        get { return null; }
    }

    public override int SaveChanges()
    {
        SaveChangesWasCalled = true;
        return -1;
    }
}

CollectionDbSet < > FakeDbSet < > ( IDbSet ObservableCollection ObservableCollection.AsQueryable()).

, NCrunch, " ".

-, , LINQ MyDataContext. , , Linq-To-Entity, Linq-To-Objects ( ).

, Ninject ​​ InMemoryDataContext MyDataContext . Ninject MyDataContext ( app.config).

if(Global.RunIntegrationTest)
    DependencyInjector.Bind<MyDataContext>().To<MyDataContext>().InSingletonScope();
else
    DependencyInjector.Bind<MyDataContext>().To<InMemoryDataContext>().InSingletonScope();

, - , .

+5
1

, , SO. , , , .

+3

All Articles