In che modo i test unitari delle persone con Entity Framework 6, dovresti preoccuparti?

Sto appena iniziando con i test unitari e il TDD in generale. Mi sono dilettato prima ma ora sono determinato ad aggiungerlo al mio stream di lavoro e scrivere software migliore.

Ho fatto una domanda ieri che ha incluso questo, ma sembra essere una domanda a parte. Mi sono seduto per iniziare a implementare una class di servizio che userò per astrarre la logica di business dai controller e mappare a specifici modelli e interazioni di dati usando EF6.

Il problema è che mi sono già bloccato perché non volevo estrarre via EF in un repository (sarà comunque disponibile al di fuori dei servizi per query specifiche, ecc.) E vorrei testare i miei servizi (verrà utilizzato il contesto EF) .

Qui credo sia la domanda, c’è un punto per farlo? In tal caso, in che modo le persone lo fanno in natura alla luce delle astrazioni che perdono causate da IQueryable e dei numerosi e fantastici messaggi di Ladislav Mrnka sul tema dei test unitari non essendo diretti a causa delle differenze nei provider di Linq quando si lavora con una memoria implementazione come applicata a un database specifico.

Il codice che voglio testare sembra piuttosto semplice. (questo è solo un codice fittizio per cercare di capire cosa sto facendo, voglio guidare la creazione usando TDD)

Contesto

public interface IContext { IDbSet Products { get; set; } IDbSet Categories { get; set; } int SaveChanges(); } public class DataContext : DbContext, IContext { public IDbSet Products { get; set; } public IDbSet Categories { get; set; } public DataContext(string connectionString) : base(connectionString) { } } 

Servizio

 public class ProductService : IProductService { private IContext _context; public ProductService(IContext dbContext) { _context = dbContext; } public IEnumerable GetAll() { var query = from p in _context.Products select p; return query; } } 

Attualmente sono nella mentalità di fare alcune cose:

  1. Mocking EF Context con qualcosa di simile a questo approccio – Mocking EF Quando Unit Testing o direttamente utilizzando un framework di simulazione sull’interfaccia come moq – prendendo il dolore che i test unitari possono passare ma non necessariamente funzionano end-to-end e li eseguono con test di integrazione?
  2. Forse usare qualcosa come Effort per deridere EF – Non l’ho mai usato e non sono sicuro che qualcuno lo stia usando in natura?
  3. Non è necessario testare nulla che richiami semplicemente a EF – quindi i metodi di servizio che chiamano EF direttamente (getAll ecc.) Non sono testati unitamente, ma solo l’integrazione testata?

Qualcuno là fuori lo sta facendo là fuori senza un Repo e avendo successo?

Questo è un argomento a cui sono molto interessato. Ci sono molti puristi che dicono che non si dovrebbero testare tecnologie come EF e NHibernate. Hanno ragione, sono già testati molto severamente e, come una risposta precedente, ha affermato che spesso è inutile spendere una grande quantità di tempo per testare ciò che non si possiede.

Tuttavia, si possiede il database sottostante! È qui che questo approccio, a mio avviso, non funziona, non è necessario testare che EF / NH svolgano correttamente il proprio lavoro. È necessario verificare che i mapping / le implementazioni funzionino con il database. Secondo me questa è una delle parti più importanti di un sistema che puoi testare.

A rigor di termini, tuttavia, ci stiamo spostando dal dominio dei test unitari ai test di integrazione, ma i principi rimangono gli stessi.

La prima cosa che devi fare è essere in grado di prendere in giro il tuo DAL in modo che il tuo BLL possa essere testato indipendentemente da EF e SQL. Questi sono i tuoi test unitari. Quindi è necessario progettare i test di integrazione per dimostrare il tuo DAL, a mio avviso questi sono altrettanto importanti.

Ci sono un paio di cose da considerare:

  1. Il tuo database deve essere in uno stato conosciuto con ogni test. La maggior parte dei sistemi utilizza un backup o crea script per questo.
  2. Ogni test deve essere ripetibile
  3. Ogni test deve essere atomico

Esistono due approcci principali per l’impostazione del database, il primo è eseguire un UnitTest per creare uno script DB. Ciò garantisce che il database di test dell’unità sarà sempre nello stesso stato all’inizio di ciascun test (è ansible reimpostarlo o eseguire ogni test in una transazione per garantire ciò).

L’altra opzione è ciò che faccio, eseguo impostazioni specifiche per ogni singolo test. Credo che questo sia l’approccio migliore per due motivi principali:

  • Il tuo database è più semplice, non è necessario un intero schema per ogni test
  • Ogni test è più sicuro, se si modifica un valore nello script di creazione non vengono invalidate decine di altri test.

Sfortunatamente il tuo compromesso qui è la velocità. Ci vuole tempo per eseguire tutti questi test, per eseguire tutti questi script di installazione / rimozione.

Un ultimo punto, può essere molto difficile scrivere una quantità così grande di SQL per testare il tuo ORM. È qui che prendo un approccio molto antipatico (i puristi qui non saranno d’accordo con me). Uso il mio ORM per creare il mio test! Piuttosto che avere uno script separato per ogni test DAL nel mio sistema, ho una fase di impostazione del test che crea gli oggetti, li collega al contesto e li salva. Quindi eseguo il mio test.

Questo è lontano dalla soluzione ideale, ma in pratica trovo che sia molto più facile da gestire (specialmente quando si hanno migliaia di test), altrimenti si sta creando un numero enorme di script. Praticità oltre la purezza.

Sicuramente guarderò indietro a questa risposta tra qualche anno (mesi / giorni) e non sarò d’accordo con me stesso, poiché i miei approcci sono cambiati – tuttavia questo è il mio approccio attuale.

Per cercare di riassumere tutto ciò che ho detto sopra questo è il mio tipico test di integrazione DB:

 [Test] public void LoadUser() { this.RunTest(session => // the NH/EF session to attach the objects to { var user = new UserAccount("Mr", "Joe", "Bloggs"); session.Save(user); return user.UserID; }, id => // the ID of the entity we need to load { var user = LoadMyUser(id); // load the entity Assert.AreEqual("Mr", user.Title); // test your properties Assert.AreEqual("Joe", user.Firstname); Assert.AreEqual("Bloggs", user.Lastname); } } 

La cosa fondamentale da notare qui è che le sessioni dei due loop sono completamente indipendenti. Nell’implementazione di RunTest è necessario assicurarsi che il contesto sia impegnato e distrutto e che i dati possano provenire dal database solo per la seconda parte.

Modifica il 13/10/2014

Ho detto che probabilmente avrei revisionato questo modello nei prossimi mesi. Mentre sostengo ampiamente l’approccio che ho sostenuto sopra, ho aggiornato leggermente il mio meccanismo di test. Ora tendo a creare le entity framework in TestSetup e TestTearDown.

 [SetUp] public void Setup() { this.SetupTest(session => // the NH/EF session to attach the objects to { var user = new UserAccount("Mr", "Joe", "Bloggs"); session.Save(user); this.UserID = user.UserID; }); } [TearDown] public void TearDown() { this.TearDownDatabase(); } 

Quindi prova ciascuna proprietà individualmente

 [Test] public void TestTitle() { var user = LoadMyUser(this.UserID); // load the entity Assert.AreEqual("Mr", user.Title); } [Test] public void TestFirstname() { var user = LoadMyUser(this.UserID); Assert.AreEqual("Joe", user.Firstname); } [Test] public void TestLastname() { var user = LoadMyUser(this.UserID); Assert.AreEqual("Bloggs", user.Lastname); } 

Ci sono diversi motivi per questo approccio:

  • Non ci sono chiamate di database aggiuntive (una configurazione, una teardown)
  • I test sono molto più granulari, ogni test verifica una proprietà
  • La logica Setup / TearDown viene rimossa dai metodi Test stessi

Ritengo che ciò renda la class di test più semplice e i test più granulari (i singoli asserimenti sono buoni )

Modifica il 5/3/2015

Un’altra revisione su questo approccio. Mentre le impostazioni a livello di class sono molto utili per test come il caricamento delle proprietà, sono meno utili dove sono richieste le diverse impostazioni. In questo caso l’impostazione di una nuova class per ogni caso è eccessiva.

Per aiutare con questo ora tendo ad avere due classi base SetupPerTest e SingleSetup . Queste due classi espongono il framework come richiesto.

In SingleSetup abbiamo un meccanismo molto simile a quello descritto nella mia prima modifica. Un esempio potrebbe essere

 public TestProperties : SingleSetup { public int UserID {get;set;} public override DoSetup(ISession session) { var user = new User("Joe", "Bloggs"); session.Save(user); this.UserID = user.UserID; } [Test] public void TestLastname() { var user = LoadMyUser(this.UserID); // load the entity Assert.AreEqual("Bloggs", user.Lastname); } [Test] public void TestFirstname() { var user = LoadMyUser(this.UserID); Assert.AreEqual("Joe", user.Firstname); } } 

Tuttavia i riferimenti che assicurano che solo le entrate corrette siano caricate possono utilizzare un approccio SetupPerTest

 public TestProperties : SetupPerTest { [Test] public void EnsureCorrectReferenceIsLoaded() { int friendID = 0; this.RunTest(session => { var user = CreateUserWithFriend(); session.Save(user); friendID = user.Friends.Single().FriendID; } () => { var user = GetUser(); Assert.AreEqual(friendID, user.Friends.Single().FriendID); }); } [Test] public void EnsureOnlyCorrectFriendsAreLoaded() { int userID = 0; this.RunTest(session => { var user = CreateUserWithFriends(2); var user2 = CreateUserWithFriends(5); session.Save(user); session.Save(user2); userID = user.UserID; } () => { var user = GetUser(userID); Assert.AreEqual(2, user.Friends.Count()); }); } } 

In sintesi entrambi gli approcci funzionano in base a ciò che si sta tentando di testare.

Sforzo di esperienza di sforzo qui

Dopo molte letture ho usato Effort nei miei test: durante i test il Context è stato costruito da una factory che restituisce una versione in memoria, che mi permette di testare ogni volta una lavagna vuota. Al di fuori dei test, la fabbrica viene risolta a uno che restituisce l’intero contesto.

Tuttavia ho la sensazione che il test su una simulazione completa del database tenda a trascinare i test verso il basso; ti rendi conto che devi occuparti di creare un sacco di dipendenze per testare una parte del sistema. Si tende anche a cercare di organizzare insieme test che potrebbero non essere correlati, solo perché c’è un solo object enorme che gestisce tutto. Se non presti attenzione, potresti trovarti a fare test di integrazione invece di test delle unità

Avrei preferito testare qualcosa di più astratto piuttosto che un enorme DBContext, ma non sono riuscito a trovare il punto debole tra test significativi e test bare-bone. Ricalca fino alla mia inesperienza.

Quindi trovo lo sforzo interessante; se hai bisogno di andare a fondo, è un buon strumento per iniziare rapidamente e ottenere risultati. Comunque penso che qualcosa di un po ‘più elegante e astratto dovrebbe essere il prossimo passo ed è quello che indagherò di seguito. Favorire questo post per vedere dove va dopo 🙂

Modifica per aggiungere : lo sforzo richiede un po ‘di tempo per riscaldarsi, quindi stai guardando ca. 5 secondi all’avvio del test. Questo potrebbe essere un problema per te se hai bisogno che la tua suite di test sia molto efficiente.


Modificato per chiarimenti:

Ho usato Effort per testare un’app webservice. Ogni messaggio M che entra viene indirizzato a IHandlerOf tramite Windsor. Castle.Windsor risolve l’ IHandlerOf che ripristina le dipendenze del componente. Una di queste dipendenze è DataContextFactory , che consente al gestore di richiedere la factory

Nei miei test istanzia direttamente il componente IHandlerOf, deriso tutti i sottocomponenti del SUT e gestisco il DataContextFactory spostato dallo sforzo sul gestore.

Significa che non eseguo test unitari in senso stretto, poiché il DB viene colpito dai miei test. Tuttavia, come ho detto sopra, mi permetta di colpire a terra e ho potuto testare rapidamente alcuni punti dell’applicazione

Se si desidera un codice di test unitario , è necessario isolare il codice che si desidera testare (in questo caso il proprio servizio) da risorse esterne (ad es. Database). Probabilmente potresti farlo con una sorta di provider EF in-memory , tuttavia un modo molto più comune è di astrarre la tua implementazione EF, ad esempio con una sorta di pattern di repository. Senza questo isolamento tutti i test che scrivi saranno test di integrazione, non test di unità.

Per quanto riguarda il test del codice EF, scrivo test di integrazione automatizzati per i miei repository che scrivono varie righe nel database durante l’inizializzazione, quindi richiamano le implementazioni del mio repository per assicurarci che si comportino come previsto (ad esempio assicurandoci che i risultati siano filtrati correttamente, o che sono ordinati nell’ordine corretto).

Si tratta di test di integrazione e non di test unitari, poiché i test si basano sulla presenza di una connessione al database e sul fatto che il database di destinazione dispone già dello schema aggiornato più recente installato.

Non vorrei codice di test unitario che non possiedo. Cosa stai testando qui, che il compilatore MSFT funziona?

Detto questo, per rendere questo codice testabile, devi quasi rendere il tuo livello di accesso ai dati separato dal tuo codice di logica aziendale. Quello che faccio è prendere tutte le mie cose EF e metterle in una (o più) class DAO o DAL che ha anche un’interfaccia corrispondente. Quindi scrivo il mio servizio che avrà l’object DAO o DAL iniettato come dipendenza (iniezione costruttore preferibilmente) a cui si fa riferimento come interfaccia. Ora la parte che deve essere testata (il tuo codice) può essere facilmente testata prendendo in giro l’interfaccia DAO e inserendola nell’istanza del servizio all’interno del test dell’unità.

 //this is testable just inject a mock of IProductDAO during unit testing public class ProductService : IProductService { private IProductDAO _productDAO; public ProductService(IProductDAO productDAO) { _productDAO = productDAO; } public List GetAllProducts() { return _productDAO.GetAll(); } ... } 

Considero i Live Data Access Layer parte dei test di integrazione, non dei test unitari. Ho visto ragazzi eseguire verifiche sul numero di viaggi effettuati nel database in ibernazione, ma si trattava di un progetto che coinvolgeva miliardi di record nel proprio archivio dati e quei viaggi extra davvero importanti.

Mi sono arrabattato qualche volta per raggiungere queste considerazioni:

1- Se la mia applicazione accede al database, perché il test non dovrebbe? Cosa succede se c’è qualcosa di sbagliato nell’accesso ai dati? I test devono saperlo in anticipo e avvertire del problema.

2- Il Pattern di Repository è un po ‘difficile e richiede tempo.

Così mi è venuto in mente questo approccio, che non penso sia il migliore, ma che abbia soddisfatto le mie aspettative:

 Use TransactionScope in the tests methods to avoid changes in the database. 

Per farlo è necessario:

1- Installa EntityFramework nel progetto di test. 2- Inserisci la stringa di connessione nel file app.config di Test Project. 3- Riferimento alla DLL System.Transactions in Test Project.

L’unico effetto collaterale è che il seed identity framework aumenterà quando si tenta di inserire, anche quando la transazione viene interrotta. Ma dal momento che i test sono fatti su un database di sviluppo, questo dovrebbe essere un problema.

Codice d’esempio:

 [TestClass] public class NameValueTest { [TestMethod] public void Edit() { NameValueController controller = new NameValueController(); using(var ts = new TransactionScope()) { Assert.IsNotNull(controller.Edit(new Models.NameValue() { NameValueId = 1, name1 = "1", name2 = "2", name3 = "3", name4 = "4" })); //no complete, automatically abort //ts.Complete(); } } [TestMethod] public void Create() { NameValueController controller = new NameValueController(); using (var ts = new TransactionScope()) { Assert.IsNotNull(controller.Create(new Models.NameValue() { name1 = "1", name2 = "2", name3 = "3", name4 = "4" })); //no complete, automatically abort //ts.Complete(); } } } 

Quindi, ecco la cosa, Entity Framework è un’implementazione quindi, nonostante il fatto che astrae la complessità dell’interazione con il database, l’interazione diretta è ancora strettamente accoppiante ed è per questo che confonde il test.

Il test delle unità consiste nel testare la logica di una funzione e ciascuno dei suoi potenziali risultati in isolamento da qualsiasi dipendenza esterna, che in questo caso è l’archivio dati. Per fare ciò, è necessario essere in grado di controllare il comportamento dell’archivio dati. Ad esempio, se si desidera affermare che la funzione restituisce false se l’utente recuperato non soddisfa alcuni criteri, il proprio archivio dati [fittato] deve essere configurato per restituire sempre un utente che non soddisfa i criteri e vice versa per l’opposta affermazione.

Detto questo, e accettando il fatto che EF sia un’implementazione, probabilmente favorirei l’idea di astrarre un repository. Sembra un po ‘ridondante? Non lo è, perché stai risolvendo un problema che sta isolando il tuo codice dall’implementazione dei dati.

In DDD, i repository restituiscono sempre radici aggregate, non DAO. In questo modo, il consumatore del repository non deve mai conoscere l’implementazione dei dati (come non dovrebbe) e possiamo utilizzarlo come esempio di come risolvere questo problema. In questo caso, l’object generato da EF è un DAO e, come tale, dovrebbe essere nascosto dalla tua applicazione. Questo è un altro vantaggio del repository che definisci. È ansible definire un object business come tipo di ritorno anziché l’object EF. Ora ciò che fa il repository è hide le chiamate a EF e mappa la risposta EF a quell’object business definito nella firma di repository. Ora è ansible utilizzare quel repository al posto della dipendenza DbContext che si inietta nelle classi e di conseguenza, ora è ansible prendere in giro quell’interfaccia per darvi il controllo di cui avete bisogno per testare il vostro codice in isolamento.

È un po ‘più di lavoro e molti hanno il naso, ma risolve un vero problema. C’è un provider in memoria che è stato menzionato in una risposta diversa che potrebbe essere un’opzione (non l’ho provato), ed è proprio l’esistenza a dimostrare la necessità della pratica.

Sono completamente in disaccordo con la risposta in alto, perché elimini il vero problema che sta isolando il codice e poi diventa tangente sulla verifica della mapping. In ogni caso, prova la tua mapping se vuoi, ma affronta il problema reale qui e ottieni una copertura del codice reale.

In breve direi di no, il succo non vale la pena lo squeeze per testare un metodo di servizio con una sola riga che recupera i dati del modello. Nella mia esperienza le persone che sono nuove a TDD vogliono testare assolutamente tutto. Il vecchio castagno di astrazione di una facciata in un framework di terze parti solo per poter creare una simulazione dell’API di framework con cui è bastardizzare / estendere in modo da poter inserire dati fittizi ha poco valore nella mia mente. Ognuno ha una visione diversa di quanto il test unitario sia il migliore. Oggi tendo ad essere più pragmatico e mi chiedo se il mio test stia davvero aggiungendo valore al prodotto finale ea quale costo.

Voglio condividere un approccio commentato e brevemente discusso, ma mostrare un esempio reale che sto attualmente utilizzando per aiutare i test di unità di servizi basati su EF.

Innanzitutto, mi piacerebbe utilizzare il provider in-memory di EF Core, ma questo riguarda EF 6. Inoltre, per altri sistemi di storage come RavenDB, sarei anche un sostenitore dei test tramite il provider di database in-memory. Di nuovo – questo è specificamente per aiutare a testare il codice basato su EF senza un sacco di cerimonie .

Ecco gli obiettivi che ho avuto quando ho inventato un pattern:

  • Deve essere semplice da capire per gli altri sviluppatori del team
  • Deve isolare il codice EF al livello più basso ansible
  • Non deve implicare la creazione di strane interfacce multi-responsabilità (come un pattern di repository “generico” o “tipico”)
  • Deve essere facile da configurare e configurare in un test di unità

Sono d’accordo con le affermazioni precedenti sul fatto che EF è ancora un dettaglio di implementazione e va bene sentire che è necessario astrarlo per fare un test unitario “puro”. Concordo anche sul fatto che, idealmente, vorrei assicurarmi che il codice EF funzioni da solo – ma questo comporta un database sandbox, provider in-memory, ecc. Il mio approccio risolve entrambi i problemi – puoi tranquillamente testare il codice dipendente da EF e creare test di integrazione per testare in modo specifico il tuo codice EF.

Il modo in cui l’ho realizzato è stato semplicemente incapsulando il codice EF in classi dedicate di Query e Command. L’idea è semplice: basta avvolgere qualsiasi codice EF in una class e dipendere da un’interfaccia nelle classi che l’avrebbero inizialmente utilizzata. Il problema principale che dovevo risolvere era evitare di aggiungere numerose dipendenze alle classi e impostare un sacco di codice nei miei test.

È qui che arriva una libreria utile e semplice: Mediatr . Permette una semplice messaggistica in-process e lo fa disaccoppiando le “richieste” dai gestori che implementano il codice. Questo ha un ulteriore vantaggio nel disaccoppiare il “cosa” dal “come”. Ad esempio, incapsulando il codice EF in piccoli blocchi, è ansible sostituire le implementazioni con un altro fornitore o con un meccanismo totalmente diverso, perché tutto ciò che si sta facendo è inviare una richiesta per eseguire un’azione.

Utilizzando l’iniezione di dipendenza (con o senza un framework – le tue preferenze), possiamo facilmente prendere in giro il mediatore e controllare i meccanismi di richiesta / risposta per abilitare il codice EF di test delle unità.

Innanzitutto, diciamo che abbiamo un servizio con logica aziendale che dobbiamo testare:

 public class FeatureService { private readonly IMediator _mediator; public FeatureService(IMediator mediator) { _mediator = mediator; } public async Task ComplexBusinessLogic() { // retrieve relevant objects var results = await _mediator.Send(new GetRelevantDbObjectsQuery()); // normally, this would have looked like... // var results = _myDbContext.DbObjects.Where(x => foo).ToList(); // perform business logic // ... } } 

Inizi a vedere il beneficio di questo approccio? Non solo si incapsula esplicitamente tutto il codice relativo all’EF in classi descrittive, si consente l’estendibilità rimuovendo l’attenzione all’implementazione di “come” viene gestita questa richiesta – a questa class non importa se gli oggetti rilevanti provengono da EF, MongoDB, o un file di testo.

Ora per la richiesta e il gestore, tramite MediatR:

 public class GetRelevantDbObjectsQuery : IRequest { // no input needed for this particular request, // but you would simply add plain properties here if needed } public class GetRelevantDbObjectsEFQueryHandler : IRequestHandler { private readonly IDbContext _db; public GetRelevantDbObjectsEFQueryHandler(IDbContext db) { _db = db; } public DbObject[] Handle(GetRelevantDbObjectsQuery message) { return _db.DbObjects.Where(foo => bar).ToList(); } } 

Come puoi vedere, l’astrazione è semplice e incapsulata. È anche assolutamente testabile perché in un test di integrazione è ansible testare questa class singolarmente – non ci sono problemi di business mescolati qui.

Che aspetto ha un test unitario del nostro servizio di funzionalità? È semplice. In questo caso, sto usando Moq per fare il mocking (usa quello che ti rende felice):

 [TestClass] public class FeatureServiceTests { // mock of Mediator to handle request/responses private Mock _mediator; // subject under test private FeatureService _sut; [TestInitialize] public void Setup() { // set up Mediator mock _mediator = new Mock(MockBehavior.Strict); // inject mock as dependency _sut = new FeatureService(_mediator.Object); } [TestCleanup] public void Teardown() { // ensure we have called or expected all calls to Mediator _mediator.VerifyAll(); } [TestMethod] public void ComplexBusinessLogic_Does_What_I_Expect() { var dbObjects = new List() { // set up any test objects new DbObject() { } }; // arrange // setup Mediator to return our fake objects when it receives a message to perform our query // in practice, I find it better to create an extension method that encapsulates this setup here _mediator.Setup(x => x.Send(It.IsAny(), default(CancellationToken)).ReturnsAsync(dbObjects.ToArray()).Callback( (GetRelevantDbObjectsQuery message, CancellationToken token) => { // using Moq Callback functionality, you can make assertions // on expected request being passed in Assert.IsNotNull(message); }); // act _sut.ComplexBusinessLogic(); // assertions } } 

Puoi vedere tutto ciò di cui abbiamo bisogno è una singola installazione e non abbiamo nemmeno bisogno di configurare nulla in più – è un test unitario molto semplice. Parliamoci chiaro: è totalmente ansible fare a meno di qualcosa come Mediatr (si dovrebbe semplicemente implementare un’interfaccia e IGetRelevantDbObjectsQuery per test, ad esempio IGetRelevantDbObjectsQuery ), ma in pratica per un grande numero di codice con molte funzionalità e query / comandi, adoro l’incapsulamento e supporto di DI innato Mediatr offre.

Se ti stai chiedendo come organizzo queste lezioni, è piuttosto semplice:

 - MyProject - Features - MyFeature - Queries - Commands - Services - DependencyConfig.cs (Ninject feature modules) 

Organizzare per feature slice è accanto al punto, ma mantiene tutti i codici rilevanti / dipendenti insieme e facilmente individuabili. Soprattutto, separo le query dai comandi, seguendo il principio Comando / Interrogazione .

Questo soddisfa tutti i miei criteri: è una cerimonia bassa, è facile da capire e ci sono ulteriori vantaggi nascosti. Ad esempio, come gestisci il salvataggio delle modifiche? Ora puoi semplificare il tuo contesto Db usando un’interfaccia di ruolo ( IUnitOfWork.SaveChangesAsync() ) e IUnitOfWork.SaveChangesAsync() le chiamate all’interfaccia del singolo ruolo o potresti incapsulare il commit / rollback all’interno dei tuoi RequestHandler – tuttavia, se preferisci farlo, dipende da te , purché sia ​​mantenibile. Ad esempio, sono stato tentato di creare una singola richiesta / gestore generico in cui si passava un object EF e si salvava / aggiornava / rimuoveva – ma devi chiedere qual è la tua intenzione e ricordare che se si voleva scambiare il gestore con un altro provider / implementazione di storage, probabilmente dovresti creare comandi / query espliciti che rappresentano ciò che intendi fare. Più spesso, un singolo servizio o funzione avrà bisogno di qualcosa di specifico – non creare roba generica prima di averne bisogno.

Ci sono ovviamente avvertenze su questo modello: puoi andare troppo lontano con un semplice meccanismo di pubblicazione / sottotitolo. I’ve limited my implementation to only abstracting EF-related code, but adventurous developers could start using MediatR to go overboard and message-ize everything–something good code review practices and peer reviews should catch. That’s a process issue, not an issue with MediatR, so just be cognizant of how you’re using this pattern.

You wanted a concrete example of how people are unit testing/mocking EF and this is an approach that’s working successfully for us on our project–and the team is super happy with how easy it is to adopt. Spero che aiuti! As with all things in programming, there are multiple approaches and it all depends on what you want to achieve. I value simplicity, ease of use, maintainability, and discoverability–and this solution meets all those demands.

I like to separate my filters from other portions of the code and test those as I outline on my blog here http://coding.grax.com/2013/08/testing-custom-linq-filter-operators.html

That being said, the filter logic being tested is not identical to the filter logic executed when the program is run due to the translation between the LINQ expression and the underlying query language, such as T-SQL. Still, this allows me to validate the logic of the filter. I don’t worry too much about the translations that happen and things such as case-sensitivity and null-handling until I test the integration between the layers.

There is Effort which is an in memory entity framework database provider. I’ve not actually tried it… Haa just spotted this was mentioned in the question!

Alternatively you could switch to EntityFrameworkCore which has an in memory database provider built-in.

https://blog.goyello.com/2016/07/14/save-time-mocking-use-your-real-entity-framework-dbcontext-in-unit-tests/

https://github.com/tamasflamich/effort

I used a factory to get a context, so i can create the context close to its use. This seems to work locally in visual studio but not on my TeamCity build server, not sure why yet.

 return new MyContext(@"Server=(localdb)\mssqllocaldb;Database=EFProviders.InMemory;Trusted_Connection=True;");