Esfuerzo: FirstOrDefault devuelve nulo al falsificar la base de datos


Pregunta

Estoy tratando de crear algunas pruebas unitarias para mi proyecto, después de mucho investigar encontré Effort, la idea es genial, se burla de la base de datos en lugar de tratar con la falsificación del DBContext que, por cierto, es muy difícil hacerlo bien cuando utilizando un esquema complejo.

Sin embargo, estoy intentando obtener el correo electrónico de un usuario después de que lo agregué específicamente a la base de datos en memoria creada por Effort, aquí está el código

MyContext contextx = new MyContext(Effort.DbConnectionFactory.CreateTransient());

var client = new Client
{
    ClientId = 2,
    PersonId = 3,
    Person = new Person
    {
        PersonId = 3,
        EMail = "xxxxx@gmail.com"
    }
};
contextx.Client.Add(client); //<-- client got added, I checked it and is there

var email = contextx.Client.Select(c => c.Person.EMail).FirstOrDefault(); 

En la última línea de arriba, no puedo devolverle el correo electrónico xxxx@gmail.com, sino que siempre devuelve un valor nulo.

¿Algunas ideas?

Respuesta aceptada

Respondiendo su pregunta directa

Para la pregunta específica que hizo, sugeriría dos cosas:

  1. Eche un vistazo a contextx.Client.ToArray() y vea cuántos miembros tiene realmente en esa colección. Puede ser que la colección del Client esté realmente vacía, en cuyo caso de hecho obtendrá nulo. O bien, podría ser que el primer elemento en la colección del Cliente tenga un valor nulo para el EMail .

  2. ¿Cómo cambia el comportamiento si llama a contextx.SaveChanges() antes de consultar la colección del Client en DbContext? Tengo curiosidad por ver si llamar a SaveChanges causará que el nuevo valor insertado exista en la colección. Esto realmente no debería ser requerido, pero podría haber alguna interacción extraña entre Effort y DbContext .

EDIT: SaveChanges() resulta ser la respuesta.

Sugerencias generales de prueba

Ya que hizo esta pregunta con la etiqueta de "prueba de unidad", ofreceré algunos consejos generales de prueba de unidad basados ​​en los diez años que pasé como practicante y entrenador de pruebas de unidad. La prueba de unidad consiste en probar varias partes pequeñas de su aplicación de forma aislada. Normalmente, esto significa que las pruebas unitarias solo interactúan con unas pocas clases a la vez. Esto también significa que las pruebas unitarias no deben depender de bibliotecas o dependencias externas (como la base de datos). A la inversa, una prueba de integración ejerce más partes del sistema a la vez y puede tener dependencias externas en cosas como bases de datos.

Si bien esto puede parecer una disputa sobre la terminología, los términos son importantes para transmitir la intención real de sus pruebas a otros miembros de su equipo.

En este caso, es posible que desee probar de forma unitaria parte de la funcionalidad que depende de DbContext, o está intentando probar su capa de acceso a datos. Si está intentando escribir una prueba de unidad aislada de algo que depende directamente del DbContext, entonces necesita romper la dependencia del DbContext. Explicaré esto a continuación en Rompiendo la dependencia en DbContext a continuación. De lo contrario, realmente está tratando de realizar una prueba de integración de su DbContext, incluido cómo se asignan sus entidades. En este caso, siempre he encontrado que es mejor aislar estas pruebas y usar una base de datos real (local). Probablemente desee utilizar una base de datos instalada localmente de la misma variedad que está utilizando en producción. A menudo, SqlExpress funciona bien. Apunte sus pruebas a una instancia de la base de datos que las pruebas pueden destruir por completo. Deje que sus pruebas eliminen los datos existentes antes de ejecutar cada prueba. Luego, pueden configurar los datos que necesiten sin preocuparse de que los datos existentes entren en conflicto.

Rompiendo la dependencia en DbContext

Entonces, ¿cómo escribe buenas pruebas unitarias cuando la lógica de su negocio depende de acceder a DbContext ? Usted no

En mis aplicaciones que usan Entity Framework para la persistencia de datos, me aseguro de que el acceso a DbContext esté contenido dentro de un proyecto de acceso a datos por separado. Por lo general, crearé clases que implementen el patrón del Repositorio y esas clases pueden tomar una dependencia en DbContext . Entonces, en este caso, crearía un ClientRepository que implemente una interfaz IClientRepository . La interfaz se vería algo así:

public interface IClientRepository {

    Client GetClientByEMail(string email);

}

Luego, cualquier clase que necesite acceso al método puede ser probada por unidad utilizando un código auxiliar básico / simulado / lo que sea. Nada tiene que preocuparse por burlarse de DbContext . Su capa de acceso a datos está contenida y puede probarla a fondo utilizando una base de datos real. Para algunas sugerencias sobre cómo probar su capa de acceso a datos, vea más arriba.

Como beneficio adicional, la implementación de esta interfaz define lo que significa encontrar un Client por dirección de correo electrónico en un lugar único y unificado. La interfaz de IClientRepository permite responder rápidamente a la pregunta "¿Cómo consultamos las entidades del Client en nuestro sistema?"

Tomar una dependencia en DbContext es aproximadamente la misma escala de un problema de prueba que permite que las clases de dominio tomen una dependencia en la cadena de conexión y tengan el código ADO.Net en todas partes. Esto significa que tiene que crear un almacén de datos real (incluso con un db falso) con datos reales en él. Pero, si contiene su acceso al DbContext dentro de un conjunto de acceso a datos específico, encontrará que las pruebas unitarias son mucho más fáciles de escribir.

En cuanto a la organización del proyecto, normalmente solo permito que mi proyecto de acceso a datos tome una referencia al Entity Framework. Tendré un proyecto Core separado en el que defino las entidades. También definiré las interfaces de acceso a datos en el proyecto Core. Luego, las implementaciones de la interfaz concreta se colocan en el proyecto de acceso a datos. La mayoría de los proyectos en su solución pueden simplemente tomar una dependencia del proyecto Core, y solo el ejecutable o proyecto web de nivel superior realmente necesita depender del proyecto de acceso a datos.





Licencia bajo: CC-BY-SA
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué