tutorial net mvc framework foreign first español data asp c# entity-framework unit-testing entity-framework-6.1

c# - net - ¿Cómo se evalúan las unidades de las personas con Entity Framework 6, en caso de que se moleste?



entity framework database first (10)

Así que aquí está la cosa, Entity Framework es una implementación, por lo que a pesar de que abstrae la complejidad de la interacción con la base de datos, la interacción directa aún es estrecha y por eso es difícil de probar.

Las pruebas unitarias consisten en probar la lógica de una función y cada uno de sus posibles resultados de forma aislada de cualquier dependencia externa, que en este caso es el almacén de datos. Para hacer eso, necesita poder controlar el comportamiento del almacén de datos. Por ejemplo, si desea afirmar que su función devuelve falso si el usuario buscado no cumple con algún conjunto de criterios, entonces su almacén de datos [simulado] debe estar configurado para devolver siempre un usuario que no cumple con los criterios, y viceversa. versa para la afirmación opuesta.

Dicho esto, y aceptando el hecho de que EF es una implementación, probablemente estaría a favor de la idea de abstraer un repositorio. Parece un poco redundante? No lo es, porque está resolviendo un problema que está aislando su código de la implementación de datos.

En DDD, los repositorios solo devuelven raíces agregadas, no DAO. De esta forma, el consumidor del repositorio nunca tiene que saber sobre la implementación de los datos (como no debería) y podemos usar eso como un ejemplo de cómo resolver este problema. En este caso, el objeto que genera EF es un DAO y, como tal, debe ocultarse de su aplicación. Este otro beneficio del repositorio que usted define. Puede definir un objeto comercial como su tipo de devolución en lugar del objeto EF. Ahora lo que hace el repositorio es ocultar las llamadas a EF y mapea la respuesta EF a ese objeto comercial definido en la firma del repositorio. Ahora puede usar ese repositorio en lugar de la dependencia de DbContext que inyecta en sus clases y, en consecuencia, ahora puede simular esa interfaz para darle el control que necesita para probar su código de forma aislada.

Es un poco más de trabajo y muchos se burlan de él, pero resuelve un problema real. Hay un proveedor en memoria que se mencionó en una respuesta diferente que podría ser una opción (no lo he probado), y su propia existencia es prueba de la necesidad de la práctica.

Estoy completamente en desacuerdo con la respuesta principal porque elude el problema real que es aislar tu código y luego sigue una tangente para probar tu mapeo. Por supuesto, pruebe su mapeo si lo desea, pero aborde el problema real aquí y obtenga una cobertura de código real.

Estoy empezando con las pruebas de Unidad y TDD en general. He incursionado antes, pero ahora estoy decidido a agregarlo a mi flujo de trabajo y escribir un mejor software.

Hice una pregunta ayer que incluyó esto, pero parece ser una pregunta en sí misma. Me he sentado para comenzar a implementar una clase de servicio que usaré para abstraer la lógica comercial de los controladores y mapear modelos específicos e interacciones de datos usando EF6.

El problema es que ya me he bloqueado el camino porque no quería abstraer EF en un repositorio (todavía estará disponible fuera de los servicios para consultas específicas, etc.) y me gustaría probar mis servicios (se utilizará el contexto EF) .

Aquí supongo que es la pregunta, ¿hay algún punto para hacer esto? Si es así, ¿cómo lo hace la gente a la luz de las extracciones de fugas causadas por IQueryable y los muchos mensajes excelentes de Ladislav Mrnka sobre el tema de las pruebas unitarias no son sencillas debido a las diferencias en los proveedores de Linq cuando se trabaja con memoria implementación como apposed a una base de datos específica.

El código que quiero probar parece bastante simple. (Esto es solo un código ficticio para tratar de entender lo que estoy haciendo, quiero conducir la creación usando TDD)

Contexto

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

Servicio

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

Actualmente estoy en la mentalidad de hacer algunas cosas:

  1. Mocking EF Context con algo como este enfoque: burlarse de EF cuando pruebas unitarias o directamente usando un marco de burla en la interfaz como moq, tomando el dolor que las pruebas de la unidad pueden pasar pero no necesariamente funcionan de extremo a extremo y respaldarlas con las pruebas de integración.
  2. Tal vez usando algo como Effort para burlarse de EF - ¿Nunca lo he usado y no estoy seguro si alguien más lo está usando en la naturaleza?
  3. No se moleste en probar nada que simplemente devuelva la llamada a EF, por lo que, básicamente, los métodos de servicio que llaman directamente a EF (getAll, etc.) no se prueban en unidades sino que solo se prueba la integración.

¿Alguien por ahí realmente está haciendo esto sin un Repo y teniendo éxito?


Effort Experience Feedback aquí

Después de leer mucho, he estado usando Effort en mis pruebas: durante las pruebas, el Contexto es construido por una fábrica que devuelve una versión en memoria, lo que me permite probar contra una pizarra en blanco cada vez. Fuera de las pruebas, la fábrica se resuelve en una que devuelve todo el contexto.

Sin embargo, tengo la sensación de que las pruebas en comparación con un simulacro completo de la base de datos tienden a arrastrar las pruebas hacia abajo; se da cuenta de que tiene que encargarse de configurar una gran cantidad de dependencias para probar una parte del sistema. También tiende a derivar hacia la organización de pruebas que pueden no estar relacionadas, simplemente porque solo hay un objeto enorme que maneja todo. Si no le presta atención, puede encontrarse haciendo pruebas de integración en lugar de pruebas unitarias

Hubiera preferido las pruebas en contra de algo más abstracto en lugar de un gran DBContext, pero no pude encontrar el punto ideal entre pruebas significativas y pruebas de bare-bone. Chalk hasta mi inexperiencia.

Así que encuentro el esfuerzo interesante; si necesita comenzar a ejecutarlo, es una buena herramienta para comenzar rápidamente y obtener resultados. Sin embargo, creo que algo un poco más elegante y abstracto debería ser el siguiente paso y eso es lo que voy a investigar a continuación. Favoritos de esta publicación para ver a dónde va después :)

Editar para agregar : Esfuerzo, toma algo de tiempo para calentar, por lo que estás viendo aprox. 5 segundos al inicio de la prueba. Esto puede ser un problema para usted si necesita que su suite de pruebas sea muy eficiente.

Editado para aclaración:

Usé Effort para probar una aplicación de servicio web. Cada mensaje M que ingresa se enruta a un IHandlerOf<M> través de Windsor. Castle.Windsor resuelve el IHandlerOf<M> que resuelve las dependencias del componente. Una de estas dependencias es DataContextFactory , que permite al controlador solicitar la fábrica

En mis pruebas instanciamos el componente IHandlerOf directamente, simulamos todos los subcomponentes del SUT y manejamos el DataContextFactory envuelto en el esfuerzo al manejador.

Significa que no realizo pruebas unitarias en un sentido estricto, ya que el DB es afectado por mis pruebas. Sin embargo, como dije antes, me permitió comenzar a ejecutar y pude probar rápidamente algunos puntos en la aplicación


En resumen, diría que no, el jugo no vale la pena apretarlo para probar un método de servicio con una sola línea que recupera los datos del modelo. En mi experiencia, las personas que son nuevas en TDD quieren probar absolutamente todo. El viejo castaño de abstraer una fachada a un framework de terceros solo para que puedas crear un simulacro de API de frameworks con los cuales bastardise / extends para que puedas inyectar datos falsos es de poco valor en mi mente. Todos tienen una visión diferente de cuánto pruebas unitarias es la mejor. Tiendo a ser más pragmático estos días y me pregunto si mi prueba realmente está agregando valor al producto final, ya qué costo.


Este es un tema en el que estoy muy interesado. Hay muchos puristas que dicen que no debe probar tecnologías como EF y NHibernate. Tienen razón, ya han sido sometidos a pruebas muy rigurosas y, como se dijo en una respuesta anterior, a menudo no tiene sentido gastar grandes cantidades de tiempo probando lo que no le pertenece.

Sin embargo, ¡usted posee la base de datos debajo! Aquí es donde, en mi opinión, se rompe este enfoque, no es necesario probar que EF / NH están haciendo su trabajo correctamente. Debe probar que sus mapeos / implementaciones funcionan con su base de datos. En mi opinión, esta es una de las partes más importantes de un sistema que puedes probar.

Estrictamente hablando, sin embargo, nos estamos alejando del dominio de las pruebas unitarias y las pruebas de integración, pero los principios siguen siendo los mismos.

Lo primero que debe hacer es burlarse de su DAL para que su BLL se pueda probar independientemente de EF y SQL. Estas son tus pruebas unitarias. A continuación, debe diseñar sus Pruebas de integración para demostrar su DAL, en mi opinión, estas son igual de importantes.

Hay un par de cosas a considerar:

  1. Su base de datos debe estar en un estado conocido con cada prueba. La mayoría de los sistemas utilizan una copia de seguridad o crean scripts para esto.
  2. Cada prueba debe ser repetible
  3. Cada prueba debe ser atómica

Hay dos enfoques principales para configurar su base de datos, el primero es ejecutar un script UnitTest create DB. Esto garantiza que la base de datos de prueba de su unidad siempre estará en el mismo estado al comienzo de cada prueba (puede restablecer esto o ejecutar cada prueba en una transacción para garantizar esto).

Tu otra opción es lo que hago, ejecutar configuraciones específicas para cada prueba individual. Creo que este es el mejor enfoque por dos razones principales:

  • Su base de datos es más simple, no necesita un esquema completo para cada prueba
  • Cada prueba es más segura, si cambia un valor en su script de creación, no invalida docenas de otras pruebas.

Lamentablemente, su compromiso aquí es la velocidad. Lleva tiempo ejecutar todas estas pruebas para ejecutar todos estos scripts de configuración / extracción.

Un último punto, puede ser muy difícil escribir una gran cantidad de SQL para probar tu ORM. Aquí es donde tomo un enfoque muy desagradable (los puristas aquí estarán en desacuerdo conmigo). ¡Uso mi ORM para crear mi prueba! En lugar de tener un script separado para cada prueba DAL en mi sistema, tengo una fase de configuración de prueba que crea los objetos, los vincula al contexto y los guarda. Luego corro mi prueba.

Esto está lejos de ser la solución ideal, sin embargo, en la práctica, me resulta mucho más fácil de administrar (especialmente cuando tienes varios miles de pruebas), de lo contrario estás creando un gran número de secuencias de comandos. Practicidad sobre la pureza.

No dudaré en volver a mirar esta respuesta en unos pocos años (meses / días) y estar en desacuerdo conmigo mismo ya que mis enfoques han cambiado, sin embargo, este es mi enfoque actual.

Para intentar resumir todo lo que he dicho anteriormente, esta es mi prueba de integración típica de BD:

[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 clave a tener en cuenta aquí es que las sesiones de los dos bucles son completamente independientes. En su implementación de RunTest debe asegurarse de que el contexto esté comprometido y destruido, y que sus datos solo puedan provenir de su base de datos para la segunda parte.

Editar 13/10/2014

Dije que probablemente revisaría este modelo en los próximos meses. Aunque en gran medida defiendo el enfoque que defendí anteriormente, he actualizado ligeramente mi mecanismo de prueba. Ahora tiendo a crear las entidades en TestSetup y 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(); }

Luego prueba cada propiedad 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); }

Hay varias razones para este enfoque:

  • No hay llamadas de base de datos adicionales (una configuración, un desmontaje)
  • Las pruebas son mucho más granulares, cada prueba verifica una propiedad
  • La lógica de configuración / TearDown se elimina de los métodos de prueba en sí

Siento que esto hace que la clase de prueba sea más simple y las pruebas más granulares (las afirmaciones simples son buenas )

Editar 5/3/2015

Otra revisión sobre este enfoque. Si bien las configuraciones de nivel de clase son muy útiles para pruebas como la carga de propiedades, son menos útiles cuando se requieren diferentes configuraciones. En este caso, establecer una nueva clase para cada caso es exagerado.

Para ayudar con esto, ahora tiendo a tener dos clases básicas, SetupPerTest y SingleSetup . Estas dos clases exponen el marco según sea necesario.

En SingleSetup tenemos un mecanismo muy similar al descrito en mi primera edición. Un ejemplo sería

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

Sin embargo, las referencias que aseguran que solo se cargan las entidades correctas pueden usar un enfoque de 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()); }); } }

En resumen, ambos enfoques funcionan en función de lo que intenta probar.


He buscado algo en algún momento para llegar a estas consideraciones:

1- Si mi aplicación accede a la base de datos, ¿por qué la prueba no debería? ¿Qué ocurre si hay algún problema con el acceso a los datos? Las pruebas deben conocerlo de antemano y alertarme sobre el problema.

2- El patrón de repositorio es algo difícil y lento.

Así que se me ocurrió este enfoque, que no creo que sea el mejor, pero cumplió con mis expectativas:

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

Para hacerlo es necesario:

1- Instalar EntityFramework en el Proyecto de prueba. 2- Ponga la cadena de conexión en el archivo app.config de Test Project. 3- Referencia al sistema dll. Transacciones en el proyecto de prueba.

El efecto secundario único es que la semilla de identidad aumentará cuando intente insertar, incluso cuando la transacción se interrumpa. Pero dado que las pruebas se realizan en una base de datos de desarrollo, esto no debería ser un problema.

Código de muestra:

[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(); } } }


No probaría un código de prueba que no sea de mi propiedad. ¿Qué estás probando aquí, que el compilador de MSFT funciona?

Dicho esto, para hacer que este código sea comprobable, casi TIENE que hacer que su capa de acceso a los datos se aparte de su código de lógica de negocios. Lo que hago es tomar todas mis cosas de EF y ponerlas en una (o múltiple) clase DAO o DAL que también tiene una interfaz correspondiente. Luego escribo mi servicio que tendrá el objeto DAO o DAL inyectado como una dependencia (preferiblemente inyección de constructor) a la que se hace referencia como interfaz. Ahora la parte que debe probarse (su código) puede probarse fácilmente burlándose de la interfaz DAO e inyectándola en su instancia de servicio dentro de la prueba de su unidad.

//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<Product> GetAllProducts() { return _productDAO.GetAll(); } ... }

Consideraría que las capas de acceso a datos en vivo forman parte de las pruebas de integración, no de las pruebas unitarias. He visto a muchachos realizar verificaciones sobre cuántos viajes a la base de datos hibernate hace antes, pero estaban en un proyecto que involucraba miles de millones de registros en su almacén de datos y esos viajes extra realmente importaban.


Quiero compartir un enfoque sobre el que se comentó y se discutió brevemente, pero se muestra un ejemplo real que estoy usando actualmente para ayudar a probar los servicios basados ​​en EF.

En primer lugar, me encantaría usar el proveedor en memoria de EF Core, pero se trata de EF 6. Además, para otros sistemas de almacenamiento como RavenDB, también sería un proponente de las pruebas a través del proveedor de la base de datos en memoria. Nuevamente, esto es específicamente para ayudar a probar el código basado en EF sin mucha ceremonia .

Estos son los objetivos que tuve cuando traté de un patrón:

  • Debe ser simple para otros desarrolladores en el equipo entender
  • Debe aislar el código EF en el nivel más bajo posible
  • No debe involucrar la creación de interfaces extra-multi-responsabilidad (como un patrón de repositorio "genérico" o "típico")
  • Debe ser fácil de configurar y configurar en una prueba de unidad

Estoy de acuerdo con las declaraciones anteriores de que EF sigue siendo un detalle de implementación y está bien sentir que necesita abstraerlo para hacer una prueba de unidad "pura". También estoy de acuerdo en que, idealmente, me gustaría asegurarme de que el código EF funcione, pero esto implica una base de datos de caja de arena, un proveedor de memoria, etc. Mi enfoque resuelve ambos problemas: puede probar de manera segura el código dependiente de EF y crear pruebas de integración para probar su código EF específicamente.

La forma en que logré esto fue simplemente encapsulando el código EF en clases dedicadas de Query y Comando. La idea es simple: simplemente envuelva cualquier código EF en una clase y dependa de una interfaz en las clases que originalmente lo usaría. El principal problema que necesitaba resolver era evitar agregar numerosas dependencias a clases y configurar un montón de código en mis pruebas.

Aquí es donde entra una biblioteca útil y simple: Mediatr . Permite mensajes simples en proceso y lo hace desacoplando "solicitudes" de los controladores que implementan el código. Esto tiene un beneficio adicional de desacoplar el "qué" del "cómo". Por ejemplo, al encapsular el código EF en pequeños fragmentos, le permite reemplazar las implementaciones con otro proveedor o mecanismo totalmente diferente, porque todo lo que hace es enviar una solicitud para realizar una acción.

Al utilizar la inyección de dependencia (con o sin un marco (su preferencia), podemos simular fácilmente el mediador y controlar los mecanismos de solicitud / respuesta para habilitar el código EF de prueba de unidad.

Primero, digamos que tenemos un servicio que tiene una lógica comercial que debemos probar:

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 // ... } }

¿Empiezas a ver el beneficio de este enfoque? No solo está encapsulando explícitamente todo el código relacionado con EF en clases descriptivas, está permitiendo la extensibilidad eliminando la preocupación de implementación de "cómo" se maneja esta solicitud; a esta clase no le importa si los objetos relevantes provienen de EF, MongoDB, o un archivo de texto.

Ahora para la solicitud y el manejador, a través de MediatR:

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

Como puede ver, la abstracción es simple y está encapsulada. También es absolutamente comprobable porque en una prueba de integración, puede probar esta clase de forma individual; no hay problemas comerciales mezclados aquí.

Entonces, ¿qué aspecto tiene una prueba unitaria de nuestro servicio de características? Es muy simple. En este caso, estoy usando Moq para hacer burlas (usar lo que sea que te haga feliz):

[TestClass] public class FeatureServiceTests { // mock of Mediator to handle request/responses private Mock<IMediator> _mediator; // subject under test private FeatureService _sut; [TestInitialize] public void Setup() { // set up Mediator mock _mediator = new Mock<IMediator>(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<DbObject>() { // 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<GetRelevantDbObjectsQuery>(), 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 } }

Puedes ver que todo lo que necesitamos es una única configuración y no necesitamos configurar nada adicional, es una prueba de unidad muy simple. Seamos claros: esto es totalmente posible sin algo como Mediatr (simplemente implementaría una interfaz y se burlaría de las pruebas, por ejemplo, IGetRelevantDbObjectsQuery ), pero en la práctica para una gran base de código con muchas características y consultas / comandos, me encanta la encapsulación y soporte de DI innato que ofrece Mediatr.

Si te preguntas cómo organizo estas clases, es bastante simple:

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

Organizar por sectores de función es algo que está al lado del punto, pero mantiene todos los códigos relevantes / dependientes juntos y fácilmente detectables. Lo que es más importante, separo las consultas frente a los comandos, siguiendo el principio de separación de comando / consulta .

Esto cumple todos mis criterios: es bajo en ceremonias, es fácil de entender y hay beneficios ocultos adicionales. Por ejemplo, ¿cómo manejas los cambios de guardado? Ahora puede simplificar su contexto Db utilizando una interfaz de rol ( IUnitOfWork.SaveChangesAsync() ) y simulando llamadas a la interfaz de rol único o puede encapsular la confirmación / reinicio dentro de sus RequestHandlers. De cualquier forma que prefiera hacerlo, depende de usted , siempre y cuando sea mantenible. Por ejemplo, tuve la tentación de crear un solo gestor / solicitud genérico en el que simplemente pasaría un objeto EF y lo guardaría / actualizaría / eliminaría, pero tiene que preguntar cuál es su intención y recordar que si quisiera swap out the handler with another storage provider/implementation, you should probably create explicit commands/queries that represent what you intend to do. More often than not, a single service or feature will need something specific--don''t create generic stuff before you have a need for it.

There are of course caveats to this pattern--you can go too far with a simple pub/sub mechanism. 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. ¡Espero que esto ayude! 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.


Si desea un código de prueba unitaria , necesita aislar el código que desea probar (en este caso, su servicio) de recursos externos (por ejemplo, bases de datos). Probablemente puedas hacer esto con algún tipo de proveedor EF en la memoria , sin embargo, una forma mucho más común es abstraer tu implementación EF, por ejemplo, con algún tipo de patrón de repositorio. Sin este aislamiento, las pruebas que escriba serán pruebas de integración, no pruebas unitarias.

En cuanto a las pruebas de código EF, escribo pruebas de integración automatizadas para mis repositorios que escriben varias filas en la base de datos durante su inicialización y luego llamo a las implementaciones de repositorio para asegurarme de que se comporten como se espera (por ejemplo, asegurarse de que los resultados se filtran correctamente o que están ordenados en el orden correcto).

Estas son pruebas de integración, no pruebas de unidades, ya que las pruebas se basan en tener una conexión de base de datos presente y en que la base de datos de destino ya tiene instalado el último esquema actualizado.


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;");