Mock Verify () Invocación

Estoy realizando pruebas unitarias para ver si se llama un método.

[Fact] public void Can_Save_Project_Changes() { //Arrange var user = new AppUser() { UserName = "JohnDoe", Id = "1" }; Mock mockRepo = new Mock(); Mock<UserManager> userMgr = GetMockUserManager(); userMgr.Setup(x => x.FindByNameAsync(It.IsAny())).ReturnsAsync(new AppUser() { UserName = "JohnDoe", Id = "1" }); var contextUser = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.NameIdentifier, user.Id), })); Mock tempData = new Mock(); ProjectController controller = new ProjectController(mockRepo.Object, userMgr.Object) { TempData = tempData.Object, ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() { User = contextUser } } }; Project project = new Project() { Name = "Test", UserID = "1", }; //Act Task result = controller.EditProject(project); //Assert mockRepo.Setup(m => m.SaveProject(It.IsAny(), user)); //This line still throws an error mockRepo.Verify(m => m.SaveProject(It.IsAny(), user)); Assert.IsType<Task>(result); var view = result.Result as ViewResult; Assert.Equal("ProjectCharts", view.ViewName); Assert.Equal("Project", view.Model.ToString()); } 

Durante la depuración, puedo verificar que el método realmente se llama en el controlador,

 //This controller line is touched walking through the code repository.SaveProject(project, user); //but this repo line is not touched public void SaveProject(Project project, AppUser user) 

La depuración en realidad no muestra la entrada al método de repository. El error exacto está debajo

Invocación esperada en el simulacro al menos una vez, pero nunca se realizó: m => m.SaveProject (, JohnDoe)

No hay configuraciones configuradas. Invocaciones realizadas: IRepository.ProjectClass IRepository.SaveProjects (ProjectClass, JohnDoe) ‘

Cuando realizo una prueba de integración real, el método SaveProject se toca en el repository y parece que funciona correctamente. También he intentado asignar cada propiedad del Project dentro de la prueba unitaria, pero obtuve el mismo resultado de error

Voy a ir un paso más allá del comentario de Yoshi.

El mensaje de Performed invocations le dice que se llamó al método, pero no con los parámetros que estaba verificando. Mi suposición basada en los mensajes es que hay algo mal con el primer parámetro.

Debería publicar la prueba para poder ser más específico.

Actualización (después de que se agregó la prueba)

Cambie userMgr.Setup para devolver su variable ‘user’, no un duplicado. A pesar de lo que dije antes, esta fue la causa de su error: el código que se estaba probando recibía un duplicado, y Moq decía correctamente que su método no había sido llamado con el user porque se había llamado con el duplicado. Así que cambiarlo a esto soluciona el problema:

 userMgr.Setup(x => x.FindByNameAsync(It.IsAny())).ReturnsAsync(user); 

Esto podría hacerse aún más fuerte si se puede evitar el uso de It.IsAny() : si la cadena específica que se espera como parámetro está configurada como parte de la configuración de la prueba, entonces proporcione el valor en su lugar.

Sospecho que las dos cadenas “1” deben ser idénticas para que esto funcione, por lo que en lugar de duplicar la cadena, declare una variable local y utilícela en lugar de ambas cadenas.

Sugeriría nunca usar valores como 1; Prefiere escribir aleatoriamente algo, para que no pase casualmente. Me refiero a imaginar un método que tome dos enteros como parámetros: al llamar a Configuración o Verificar para ese método, si usa el mismo valor para ambos enteros, la prueba podría pasar incluso si su código ha cambiado los valores por error ( pasando cada uno al parámetro incorrecto). Si usa valores diferentes al llamar a Configuración o Verificar, entonces solo funcionará cuando se pase el valor correcto en el parámetro correcto.

mockRepo.Setup es redundante. La configuración le permite especificar cómo se comporta la clase, pero no hay nada más después de eso en la línea, por lo que es redundante y puede eliminarse. Algunas personas usan la configuración junto con VerifyAll, pero es posible que desee leer esta discusión sobre el uso de VerifyAll .

Ahora cambie su verificación de nuevo a usar el project lugar de It.IsAny() . Esperaría que funcionara.

Actualización 2

Considere un techo de tejas. Cada azulejo es responsable de proteger una pequeña parte del techo, superponiéndose ligeramente a las que están debajo. Ese techo de tejas es como una colección de pruebas unitarias cuando se usa la burla.

Cada ’tile’ representa un accesorio de prueba, que cubre una clase en el código real. La ‘superposición’ representa la interacción entre la clase y las cosas que utiliza, que debe definirse utilizando simulaciones, que se prueban usando elementos como Configurar y Verificar (en Moq).

Si esta burla se hace mal, entonces las brechas entre las fichas serán grandes, y su techo podría tener fugas (es decir, su código podría no funcionar). Dos ejemplos de cómo burlarse se puede hacer mal:

  1. No verifica los parámetros que se le dan a las dependencias, al usar It.IsAny cuando realmente no es necesario.
  2. Definición incorrecta del comportamiento del simulacro en comparación con cómo se comportaría la dependencia real.

Ese último es tu mayor riesgo; pero no es diferente al riesgo de escribir pruebas unitarias malas (independientemente de si implica burla). Si escribiera una prueba unitaria que ejerciera el código bajo prueba pero luego no hiciera ninguna afirmación, o hiciera una afirmación sobre algo que no importa, esa sería una prueba débil. Usar It.IsAny es como decir “No me importa qué es este valor”, y significa que te estás perdiendo la oportunidad de afirmar cuál debe ser ese valor.

Hay momentos en que no es posible especificar el valor, donde tiene que usar It.IsAny , y otro caso al que volveré en un segundo también es correcto. De lo contrario, siempre debe tratar de especificar cuáles son los parámetros, ya sea exactamente, o al menos usar It.Is(comparison lambda) . La otra vez está bien usar It.IsAny() es cuando está verificando que no se ha realizado una llamada, usando Times.Never como parámetro para Verify . En este caso, generalmente es una buena idea usarlo siempre, ya que verifica que la llamada no se haya realizado con ningún parámetro (evitando la posibilidad de que simplemente haya cometido un error sobre los parámetros que se proporcionan).

Si escribí algunas pruebas de unidad que me dieron una cobertura de código del 100%; pero no probó todos los escenarios posibles, sería una prueba unitaria débil. ¿Tengo alguna prueba para tratar de encontrar estas pruebas mal escritas? No, y las personas que no usan burlas tampoco tienen pruebas como esa.

Volviendo a la analogía del techo embaldosado … si no me burlaba, y tenía que probar cada parte usando las dependencias reales, así es como se vería mi techo. Podría tener una ficha para todos los bits en el borde inferior del techo. No hay problema hasta el momento. Para lo que hubiera sido el siguiente juego de tejas en el techo, para lo que hubiera sido una teja, necesito una teja triangular, cubriendo donde debería haber ido esa teja, y cubriendo las tejas debajo de ella (aunque ya están cubiertas por un azulejo). Aún así, no está mal. Pero 15 tejas más arriba en el techo, esto va a ser agotador.

Llevando eso a un escenario del mundo real, imagine que estoy probando un código de cliente, que usa dos servicios WCF, uno de los cuales es un tercero que cobra por uso, uno de los cuales está protegido por la autenticación de Windows, tal vez uno de esos servicios tiene lógica compleja en su capa de negocios antes de llegar a la capa de datos e interactuar con una base de datos, y en algún lugar allí, podría tener algo de almacenamiento en caché. Me atrevo a decir que escribir pruebas decentes para esto sin burlarse podría describirse como excesivamente intrincado, si es posible (en la vida de una persona) …

A menos que uses burlas, lo que te permite …

  1. Pruebe su código que depende del código de un tercero, sin realizar llamadas (reconociendo los riesgos mencionados anteriormente sobre burlarse de eso con precisión).
  2. Simule lo que sucedería si un usuario con o sin los permisos adecuados llama al servicio WCF protegido (piense en cómo lo haría desde pruebas automatizadas sin burlarse)
  3. Pruebe las partes separadas del código de forma aislada, lo que es particularmente valioso cuando se trata de una lógica empresarial compleja. Esto reduce exponencialmente el número de rutas a través del código que deben probarse, reduciendo el costo de escribir las pruebas y de mantenerlas. Imagine la complejidad de tener que configurar la base de datos con todos los requisitos previos, no solo para las pruebas de capa de datos, sino para todas las pruebas de la stack de llamadas. Ahora, ¿qué sucede cuando hay un cambio de base de datos?
  4. Prueba el almacenamiento en caché mediante la verificación de cuántas veces se invocó el método de simulación.

(Para el registro, la velocidad de ejecución de las pruebas nunca ha jugado ningún papel en mi decisión de usar la burla).

Afortunadamente, la burla es simple, requiriendo apenas cualquier nivel de comprensión por encima de lo que he explicado aquí. Siempre que reconozca que el uso de la burla es un compromiso en comparación con las pruebas de integración completas, genera el tipo de ahorro en el tiempo de desarrollo y mantenimiento que cualquier gerente de producto agradecerá. Intenta mantener pequeños los espacios entre tus fichas.

Intenta configurar tu método así:

mockRepo.Setup (m => m.SaveProject (It.IsAny (), It.IsAny ())

Y luego verifique el uso de It.IsAny también.

O simplemente use It.IsAny para los parámetros que no quiere (o no puede) verificar correctamente por algún motivo. También puede crear combinaciones personalizadas en el caso posterior.

Como se menciona en otros comentarios. Es probable que el problema esté en los argumentos que ha configurado que se burlan de esperar.