2012-06-14 29 views
5

Esto es más una solución/solución de problemas que una pregunta real. Lo estoy publicando aquí porque no pude encontrar esta solución en el desbordamiento de la pila o, de hecho, después de mucha búsqueda en Google.Pruebas unitarias con EF Code First DataContext

El problema:

tengo una MVC 3 webapp utilizando el código de EF 4 primero que quiero escribir pruebas unitarias para. También estoy usando NCrunch para ejecutar las pruebas unitarias sobre la marcha mientras codigo, así que me gustaría evitar respaldarlas en una base de datos real aquí.

Otras soluciones:

IDataContext

he encontrado esto de la forma más aceptada para crear un DataContext en la memoria. De hecho, implica escribir una interfaz IMyDataContext para su MyDataContext y luego usar la interfaz en todos sus controladores. Un ejemplo de esto es here.

Esta es la ruta que seguí inicialmente e incluso llegué a escribir una plantilla T4 para extraer IMyDataContext de MyDataContext, ya que no me gusta tener que mantener el código dependiente duplicado.

Sin embargo, descubrí rápidamente que algunas sentencias de Linq fallan en la producción cuando se utiliza IMyDataContext en lugar de MyDataContext. En concreto consultas como esta una excepción 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 }; 

mi solución

Este fue en realidad bastante simple. Simplemente creado una subclase MyInMemoryDataContext a MyDataContext y hizo caso omiso de todos los IDbSet < ..> propiedades como abajo:

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; 
    } 
} 

En este caso mi CollectionDbSet <> es una versión ligeramente modificada de FakeDbSet <>here (que simplemente implementa IDbSet con un subyacente ObservableCollection y ObservableCollection.AsQueryable()).

Esta solución funciona muy bien con todas mis pruebas unitarias y específicamente con NCrunch ejecutando estas pruebas sobre la marcha.

Integración completa Pruebas de

Estas pruebas Test Unit toda la lógica de negocio, pero un inconveniente importante es que ninguno de sus estados de LINQ están garantizados para trabajar con su MyDataContext real. Esto se debe a que las pruebas en un contexto de datos en memoria significa que está reemplazando el proveedor Linq-To-Entity pero un proveedor Linq-To-Objects (como se señaló muy bien en la respuesta a la pregunta this SO).

Para arreglar esto, utilizo Ninject dentro de mis pruebas de unidad y configuro InMemoryDataContext para enlazar en lugar de MyDataContext dentro de mis pruebas unitarias. A continuación, puede usar Ninject para vincularse a un MyDataContext real al ejecutar las pruebas de integración (a través de una configuración en la aplicación.config).

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

Avíseme, si usted tiene algún comentario al respecto, sin embargo, siempre hay mejoras que hacer.

+1

[aquí] (http://stackoverflow.com/questions/4128640) es una pregunta StackOverflow sobre esto, y [aquí es un artículo] (http://www.cuttingedge.it/blogs/steven/pivot/ entry.php? id = 84) que describe otra solución a este problema. – Steven

+1

[aquí] (http://stackoverflow.com/questions/10967921/decouple-ef-queries-from-bl-extension-methods-vs-class-per-query) es otra cuestión reciente sobre el mismo tema. –

Respuesta

3

Según mi comentario en la pregunta, esto fue más para ayudar a otros a buscar este problema en SO. Pero como se señala en los comentarios debajo de la pregunta, existen bastantes otros enfoques de diseño que solucionarían este problema.

Cuestiones relacionadas