Fake DbContext di Entity Framework 4.1 da testare

Sto usando questo tutorial per simulare il mio DbContext e testare: http://refactorthis.wordpress.com/2011/05/31/mock-faking-dbcontext-in-entity-framework-4-1-with-a-generic -repository /

Ma devo cambiare l’implementazione di FakeMainModuleContext da usare nei miei controller:

public class FakeQuestiona2011Context : IQuestiona2011Context { private IDbSet _credencial; private IDbSet _perfil; private IDbSet _apurador; private IDbSet _entrevistado; private IDbSet _setor; private IDbSet _secretaria; private IDbSet _pesquisa; private IDbSet _pergunta; private IDbSet _resposta; public IDbSet Credencial { get { return _credencial ?? (_credencial = new FakeDbSet()); } set { } } public IDbSet Perfil { get { return _perfil ?? (_perfil = new FakeDbSet()); } set { } } public IDbSet Apurador { get { return _apurador ?? (_apurador = new FakeDbSet()); } set { } } public IDbSet Entrevistado { get { return _entrevistado ?? (_entrevistado = new FakeDbSet()); } set { } } public IDbSet Setor { get { return _setor ?? (_setor = new FakeDbSet()); } set { } } public IDbSet Secretaria { get { return _secretaria ?? (_secretaria = new FakeDbSet()); } set { } } public IDbSet Pesquisa { get { return _pesquisa ?? (_pesquisa = new FakeDbSet()); } set { } } public IDbSet Pergunta { get { return _pergunta ?? (_pergunta = new FakeDbSet()); } set { } } public IDbSet Resposta { get { return _resposta ?? (_resposta = new FakeDbSet()); } set { } } public void SaveChanges() { // do nothing (probably set a variable as saved for testing) } } 

E il mio test in questo modo:

 [TestMethod] public void IndexTest() { IQuestiona2011Context fakeContext = new FakeQuestiona2011Context(); var mockAuthenticationService = new Mock(); var apuradores = new List { new Apurador() { Matricula = "1234", Nome = "Acaz Souza Pereira", Email = "[email protected]", Ramal = "1234" }, new Apurador() { Matricula = "4321", Nome = "Samla Souza Pereira", Email = "[email protected]", Ramal = "4321" }, new Apurador() { Matricula = "4213", Nome = "Valderli Souza Pereira", Email = "[email protected]", Ramal = "4213" } }; apuradores.ForEach(apurador => fakeContext.Apurador.Add(apurador)); ApuradorController apuradorController = new ApuradorController(fakeContext, mockAuthenticationService.Object); ActionResult actionResult = apuradorController.Index(); Assert.IsNotNull(actionResult); Assert.IsInstanceOfType(actionResult, typeof(ViewResult)); ViewResult viewResult = (ViewResult)actionResult; Assert.IsInstanceOfType(viewResult.ViewData.Model, typeof(IndexViewModel)); IndexViewModel indexViewModel = (IndexViewModel)viewResult.ViewData.Model; Assert.AreEqual(3, indexViewModel.Apuradores.Count); } 

Lo sto facendo bene?

Purtroppo non lo stai facendo bene perché quell’articolo è sbagliato. Finge che FakeContext renderà la tua unità di codice testabile ma non lo farà. Una volta che esponi IDbSet o IQueryable al tuo controller e hai falsificato il set con la raccolta in memoria, non puoi mai essere sicuro che il tuo test dell’unità verifichi realmente il tuo codice. È molto semplice scrivere una query LINQ nel controller che supererà il test dell’unità (poiché FakeContext utilizza LINQ-to-Objects) ma non riesce in fase di esecuzione (poiché il tuo contesto reale utilizza LINQ-to-Entities). Ciò rende inutile l’intero scopo del test dell’unità.

La mia opinione: non preoccuparti di simulare il contesto se vuoi esporre i set al controller. Utilizzare invece test di integrazione con database reale per il test. Questo è l’unico modo in cui convalidare le query LINQ definite nel controller come previsto.

Certo, se vuoi chiamare solo ToList o FirstOrDefault sui tuoi set, il tuo FakeContext ti servirà bene ma una volta che fai qualcosa di più complesso puoi trovare una trappola molto presto (basta mettere la stringa “Non può essere tradotta in un’espressione di negozio” in Google – tutti questi problemi appariranno solo quando eseguirai Linq-to-entities ma passeranno i tuoi test con Linq-to-objects).

Questa è una domanda abbastanza comune in modo da poter controllare alcuni altri esempi:

  • Per restituire IQueryable o non restituire IQueryable
  • Unità di test DbContext
  • ASP.NET MVC3 ed Entity Framework Code prima architettura
  • Organizzazione, dove dovrei inserire le domande più comuni quando si utilizza Entity Framework Code First?
  • È ansible bloccare il contesto e le classi di Entity Framework per testare il livello di accesso ai dati?

“Sfortunatamente non lo stai facendo bene perché quell’articolo è sbagliato, fa finta che FakeContext renderà la tua unità di codice testabile ma non lo farà”

Sono il creatore del post del blog a cui ti riferisci. Il problema che vedo qui è un fraintendimento dei fondamenti dei test delle unità N-Layered. Il mio post non è pensato per essere utilizzato direttamente per testare la logica del controller.

Un test unitario dovrebbe fare esattamente come suggerisce il nome e testare “One Unit”. Se sto testando un controller (come fai sopra), dimentico tutto sull’accesso ai dati. Dovrei rimuovere tutte le chiamate al contesto del database nella mia mente e sostituirle con una chiamata al metodo black box come se quelle operazioni fossero a me sconosciute. È il codice attorno a quelle operazioni che sono interessato a testare.

Esempio:

Nella mia applicazione MVC utilizziamo il modello di repository. Ho un repository, diciamo CustomerRepository: ICustomerRepository, che eseguirà tutte le operazioni del mio database clienti.

Se dovessi testare i miei controller, vorrei che i test testassero il mio repository, il mio accesso al database e la logica del controllore stesso? ovviamente no! ci sono molte “unità” in questa pipeline. Quello che vuoi fare è creare un repository fasullo che implementa ICustomerRepository per permetterti di testare la logica del controller da solo.

Questo al meglio delle mie conoscenze non può essere fatto solo sul contesto del database. (eccetto forse per l’utilizzo di Microsoft Moles, che puoi controllare se vuoi). Questo è semplicemente perché tutte le query vengono eseguite al di fuori del contesto nella class controller.

Se volessi testare la logica del CustomerRepository come potrei farlo? Il modo più semplice è usare un contesto falso. Questo mi consentirà di assicurarmi che quando cerco di ottenere un cliente tramite ID, in realtà ottenga il cliente tramite ID e così via. I metodi di deposito sono molto semplici e il problema “Non può essere tradotto in un’espressione di archivio” di solito non affiorerà. Anche se in alcuni casi minori può (a volte a causa di query linq scritte in modo errato) in questi casi è importante eseguire anche test di integrazione che testeranno il tuo codice fino al database. Questi problemi si troveranno nei test di integrazione. Ho usato questa tecnica N-Layered da un po ‘di tempo e non ho riscontrato problemi con questo.

Test di integrazione

Ovviamente testare la tua app contro il database stesso è un esercizio costoso e una volta che hai ottenuto decine di migliaia di test diventa un incubo, d’altra parte è meglio imitare il modo in cui il codice verrà utilizzato nel “mondo reale”. Questi test sono importanti (dall’interfaccia utente al database) e verranno eseguiti come parte dei test di integrazione, NON test delle unità.

Acaz, ciò di cui hai veramente bisogno nel tuo scenario è un repository che è beffardo / simulabile. Se si desidera testare i controller come si sta facendo allora il controller dovrebbe prendere in un object che avvolge la funzionalità del database. Quindi può restituire tutto ciò di cui hai bisogno per testare tutti gli aspetti delle funzionalità del tuo controller.

vedere http://msdn.microsoft.com/en-us/library/ff714955.aspx

Per testare il repository stesso (discusso se necessario in tutti i casi), si vorrà o fingere il contesto o utilizzare qualcosa sulla falsariga del framework ‘Moles’.

LINQ è intrinsecamente difficile da testare. Il fatto che la query sia definita al di fuori del contesto utilizzando i metodi di estensione ci dà una grande flessibilità, ma crea un incubo di prova. Avvolgi il tuo contesto in un repository e questo problema scompare.

scusa tanto tempo 🙂

Come menzionato da Ladislav Mrnka, dovresti testare Linq-to-Entity ma non Linq-to-Object. Normalmente usavo Sql CE come DB di test e ricreavo sempre il database prima di ogni test. Questo potrebbe rendere un po ‘lento il test, ma finora sono d’accordo con le prestazioni dei miei 100+ test unitari.

Innanzitutto, modifica l’impostazione della stringa di connessione con SqlCe in App.config del tuo progetto di test.

    

Secondo, imposta l’inizializzatore db con DropCreateDatabaseAlways .

 Database.SetInitializer(new DropCreateDatabaseAlways()); 

E quindi, forzare EF per inizializzare prima di eseguire ogni test.

 public void Setup() { Database.SetInitializer(new DropCreateDatabaseAlways()); context = new MyDbContext(); context.Database.Initialize(force: true); } 

Se stai usando xunit, chiama il metodo Setup nel tuo costruttore. Se stai usando MSTest, metti TestInitializeAttribute su quel metodo. Se nunit …….

È ansible creare un DbContext falso utilizzando Effort per EF 6+. Vedi https://effort.codeplex.com/ . Effort sta per E ntity F ramework F ake O bjectContext R ealizzazione T ool.

Per un articolo con un campione funzionante, vedere http://www.codeproject.com/Tips/1036630/Using-Effort-Entity-Framework-Unit-Testing-Tool o http://www.codeproject.com/Articles/ 460175 / Two-strategies-for-testing-Entity-Framework-Effort? Msg = 5122027 # xx5122027xx .

So che non dovremmo farlo, ma a volte devi comunque (il tuo capo potrebbe chiederti anche tu ad esempio e non cambierebbe idea).

Quindi, come ho dovuto farlo, lo lascio qui potrebbe aiutare alcune persone. Sono abbastanza nuovo di c # / .net e tutto quindi è lontano dall’essere ottimizzato / pulito, suppongo, ma sembra funzionare.

seguente MSDN Trova qui la class mancante e usando un po ‘di riflessione sono riuscito ad aggiungere proprietà one way: gli elementi chiave qui sono AddNavigationProperty e RefreshNavigationProperties . Se qualcuno ha suggerito di migliorare questo codice, li prenderò volentieri

 using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using System.Linq.Expressions; namespace MockFactory { public class TestDbSet : DbSet, IQueryable, IEnumerable, IDbAsyncEnumerable where TEntity : class { public readonly ObservableCollection _data; private readonly IQueryable _query; private readonly Dictionary entities; public TestDbSet() { _data = new ObservableCollection(); _query = _data.AsQueryable(); entities = new Dictionary(); } public override ObservableCollection Local { get { return _data; } } IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() { return new TestDbAsyncEnumerator(_data.GetEnumerator()); } IEnumerator IEnumerable.GetEnumerator() { return _data.GetEnumerator(); } Type IQueryable.ElementType { get { return _query.ElementType; } } Expression IQueryable.Expression { get { return _query.Expression; } } IQueryProvider IQueryable.Provider { get { return new TestDbAsyncQueryProvider(_query.Provider); } } IEnumerator IEnumerable.GetEnumerator() { return _data.GetEnumerator(); } public void AddNavigationProperty(DbSet dbSet) where T : class { entities.Add(typeof (T), dbSet); } public void RefreshNavigationProperty(TEntity item) { foreach (var entity in entities) { var property = item.GetType().GetProperty(entity.Key.Name); var type = (int)item.GetType().GetProperty(entity.Key.Name.Replace(typeof(TEntity).Name, "")).GetValue(item); var dbSets = (IEnumerable)entity.Value.GetType().GetField("_data").GetValue(entity.Value); var dbSet = dbSets.Single(x => (int)x.GetType().GetProperty("Id").GetValue(x) == type); property.SetValue(item, dbSet); } } public override TEntity Add(TEntity item) { RefreshNavigationProperty(item); _data.Add(item); return item; } public override TEntity Remove(TEntity item) { _data.Remove(item); return item; } public override TEntity Attach(TEntity item) { _data.Add(item); return item; } public override TEntity Create() { return Activator.CreateInstance(); } public override TDerivedEntity Create() { return Activator.CreateInstance(); } } } 

Puoi quindi creare il tuo contesto

  public TestContext() { TypeUsers = new TestDbSet(); StatusUsers = new TestDbSet(); TypeUsers.Add(new TypeUser {Description = "FI", Id = 1}); TypeUsers.Add(new TypeUser {Description = "HR", Id = 2}); StatusUsers.Add(new StatusUser { Description = "Created", Id = 1 }); StatusUsers.Add(new StatusUser { Description = "Deleted", Id = 2 }); StatusUsers.Add(new StatusUser { Description = "PendingHR", Id = 3 }); Users = new TestDbSet(); ((TestDbSet) Users).AddNavigationProperty(StatusUsers); ((TestDbSet)Users).AddNavigationProperty(TypeUsers); } public override DbSet TypeUsers { get; set; } public override DbSet StatusUsers { get; set; } public override DbSet Users { get; set; } public int SaveChangesCount { get; private set; } public override int SaveChanges(string modifierId) { SaveChangesCount++; return 1; } } 

Infine, non dimenticare nel tuo test di aggiornare le proprietà di navigazione prima di fare l’assert (dovrebbe esserci un modo migliore ma non riuscivo a trovarlo)

 ContextFactory.Entity.Users.Each(((TestDbSet) ContextFactory.Entity.Users).RefreshNavigationProperty);