2012-06-14 19 views
5

Questa è più una soluzione/lavoro che una domanda reale. Sto postando qui da quando non sono riuscito a trovare questa soluzione su overflow dello stack o in effetti dopo un sacco di Google.Test unitari con codice EF First DataContext

Il problema:

Ho una webapp 3 MVC utilizzando il codice EF 4 prima che voglio scrivere unit test per. Sto anche usando NCrunch per eseguire i test di unità al volo mentre codice, quindi mi piacerebbe evitare di tornare su un vero database qui.

altre soluzioni:

IDataContext

ho trovato questo il modo più accettato di creare un DataContext in memoria. Comporta efficacemente la scrittura di un'interfaccia IMyDataContext per il tuo MyDataContext e quindi l'utilizzo dell'interfaccia in tutti i tuoi controller. Un esempio di ciò è here.

Questo è il percorso che ho seguito inizialmente e sono persino andato a scrivere un modello T4 per estrarre IMyDataContext da MyDataContext poiché non mi piace dover mantenere il codice dipendente duplicato.

Tuttavia, ho scoperto rapidamente che alcune istruzioni Linq non sono in produzione quando si utilizza IMyDataContext anziché MyDataContext. In particolare query come questo gettare un 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 }; 

mia soluzione

Questo era in realtà piuttosto semplice. Ho semplicemente creato una sottoclasse MyInMemoryDataContext per MyDataContext e calpestato tutti i IDbSet < ..> proprietà come qui sotto:

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

In questo caso il mio CollectionDbSet <> è una versione leggermente modificata di FakeDbSet <>here (che implementa semplicemente IDbSet con una sottostante ObservableCollection e ObservableCollection.AsQueryable()).

Questa soluzione funziona bene con tutti i miei test di unità e in particolare con NCrunch che esegue questi test al volo.

Integrazione completa Test

Questi test unità di prova tutta la logica di business, ma uno svantaggio importante è che nessuna delle sue dichiarazioni LINQ sono garantiti per funzionare con il vostro MyDataContext reale. Questo perché testare un contesto di dati in memoria significa sostituire il provider Linq-To-Entity ma un provider Linq-To-Objects (come indicato molto bene nella risposta alla domanda this SO).

Per risolvere questo problema, utilizzo il test Ninject all'interno della mia unità e collaudo InMemoryDataContext per collegare invece MyDataContext all'interno dei miei test di unità. È quindi possibile utilizzare Ninject per collegarsi a un effettivo MyDataContext durante l'esecuzione dei test di integrazione (tramite un'impostazione nell'app.config).

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

fatemi sapere se avete tutte le risposte su questo però, ci sono sempre miglioramenti da apportare.

+1

[Qui] (http://stackoverflow.com/questions/4128640) è una domanda StackOverflow su questo, e [qui è un articolo] (http://www.cuttingedge.it/blogs/steven/pivot/ entry.php? id = 84) che descrive un'altra soluzione a questo problema. – Steven

+1

[Qui] (http://stackoverflow.com/questions/10967921/decouple-ef-queries-from-bl-extension-methods-vs-class-per-query) è un'altra domanda recente sullo stesso argomento. –

risposta

3

Come per il mio commento nella domanda, questo era più per aiutare gli altri alla ricerca di questo problema su SO. Ma come sottolineato nei commenti al di sotto della domanda ci sono alcuni altri approcci progettuali che potrebbero risolvere questo problema.