Verify Logging
La clase LoggerExtensions
proporciona métodos de extensión para facilitar la verificación del registro de logs utilizando ILogger
en pruebas unitarias con xUnit y Moq. Su propósito principal es simplificar las pruebas de componentes que utilizan logging.
namespace CodeDesignPlus.Net.xUnit.Extensions;
/// <summary>/// Methods extensions to ILooger/// </summary>/// <remarks>https://adamstorr.azurewebsites.net/blog/mocking-ilogger-with-moq</remarks>public static class LoggerExtensions{ /// <summary> /// Verify Logging /// </summary> /// <typeparam name="T">The type who's name is used for the logger category name.</typeparam> /// <param name="logger">ILogger</param> /// <param name="expectedMessage">Expected Message</param> /// <param name="expectedLogLevel">Expected Log Level</param> /// <param name="times">Times</param> /// <returns>Return Logger</returns> public static Mock<ILogger<T>> VerifyLogging<T>(this Mock<ILogger<T>> logger, string expectedMessage, LogLevel expectedLogLevel = LogLevel.Debug, Times? times = null) { times ??= Times.Once();
Func<object, Type, bool> state = (v, t) => v.ToString()!.CompareTo(expectedMessage) == 0;
logger.Verify( x => x.Log( It.Is<LogLevel>(l => l == expectedLogLevel), It.IsAny<EventId>(), It.Is<It.IsAnyType>((v, t) => state(v, t)), It.IsAny<Exception>(), It.Is<Func<It.IsAnyType, Exception, string>>((v, t) => true)), (Times)times);
return logger; }
/// <summary> /// Verifica que el método Log del ILogger fue invocado al menos una vez. /// </summary> /// <typeparam name="T">El tipo usado para el nombre de categoría del logger.</typeparam> /// <param name="logger">El mock del ILogger.</param> public static void VerifyLoggerWasCalled<T>(this Mock<ILogger<T>> logger) { logger.Verify(l => l.Log( It.IsAny<LogLevel>(), It.IsAny<EventId>(), It.IsAny<It.IsAnyType>(), It.IsAny<Exception>(), It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.AtLeastOnce()); }}
Dependencias y Requisitos Previos
Microsoft.Extensions.Logging
: Necesario para las interfaces y enumeraciones de logging (ILogger
,LogLevel
, etc.).Moq
: Necesario para la creación de mocks deILogger
.System
: Necesario para lasFunc
y el tipoType
.
Escenarios de uso
Esta clase es particularmente útil en pruebas unitarias (xUnit) donde se necesita:
- Verificar que se registran los logs esperados por los componentes.
- Asegurar que se usan los niveles de log correctos.
- Probar diferentes escenarios de logging.
- Simplificar el uso de
Moq
para verificar las llamadas aILogger
.
Beneficios
- Simplifica las pruebas: Reduce la verbosidad en la verificación de logs, haciendo las pruebas más legibles.
- Facilidad de uso: Proporciona métodos de extensión que se pueden usar directamente en los mocks de
ILogger
. - Verificación precisa: Permite verificar tanto el mensaje como el nivel de log, e incluso la cantidad de veces que se llamó.
- Aumenta la confiabilidad: Asegura que los componentes registren información relevante para el debugging y monitoreo.
Ejemplo Práctico
Este ejemplo muestra cómo usar LoggerExtensions
para verificar el logging en un servicio:
using Xunit;using Microsoft.Extensions.Logging;using Moq;using CodeDesignPlus.Net.xUnit.Helpers;
public class MyServiceTests{ [Fact] public void MyService_DoesSomething_LogsExpectedMessage() { // Arrange var loggerMock = new Mock<ILogger<MyService>>(); var myService = new MyService(loggerMock.Object);
// Act myService.DoSomething();
// Assert loggerMock.VerifyLogging("Doing something...", LogLevel.Information); loggerMock.VerifyLoggerWasCalled(); }
[Fact] public void MyService_ThrowsException_LogsError() { // Arrange var loggerMock = new Mock<ILogger<MyService>>(); var myService = new MyService(loggerMock.Object);
//Act try { myService.ThrowException(); } catch(Exception){}
// Assert loggerMock.VerifyLogging("An error occurred", LogLevel.Error); loggerMock.VerifyLoggerWasCalled(); }}
public class MyService{ private readonly ILogger<MyService> _logger;
public MyService(ILogger<MyService> logger) { _logger = logger; }
public void DoSomething() { _logger.LogInformation("Doing something..."); //do something }
public void ThrowException() { try { throw new Exception("Error"); } catch(Exception ex) { _logger.LogError(ex, "An error occurred"); throw; } }}
En este ejemplo:
- Se crea un mock de
ILogger<MyService>
. - Se instancia
MyService
con el mock del logger. - Se ejecutan las acciones que se quieren probar.
- Se utilizan los métodos de extensión
VerifyLogging
yVerifyLoggerWasCalled
para asegurar que el logger funciona como se espera.
Métodos de extensión
La clase LoggerExtensions
proporciona los siguientes métodos de extensión para verificar el logging:
VerifyLogging
El método VerifyLogging
verifica que el logger se llamó con un mensaje y nivel de log específicos.
public static Mock<ILogger<T>> VerifyLogging<T>(this Mock<ILogger<T>> logger, string expectedMessage, LogLevel expectedLogLevel = LogLevel.Debug, Times? times = null)
-
Parámetros:
logger
: El mock deILogger<T>
en el que se realizará la verificación.expectedMessage
: El mensaje de log esperado.expectedLogLevel
: El nivel de log esperado (por defecto esLogLevel.Debug
).times
: La cantidad de veces que se espera que se registre el log (por defecto esTimes.Once()
).
-
Tipo de retorno:
Mock<ILogger<T>>
: El mismo mock deILogger<T>
para permitir el encadenamiento de métodos.
-
Ejemplos de código:
Este ejemplo muestra cómo verificar que un mensaje se registró con nivel
Information
una vez.var loggerMock = new Mock<ILogger<MyService>>();loggerMock.VerifyLogging("My Info Message", LogLevel.Information);Este ejemplo muestra cómo verificar que un mensaje se registró con nivel
Error
dos veces.var loggerMock = new Mock<ILogger<MyService>>();loggerMock.VerifyLogging("My Error Message", LogLevel.Error, Times.Exactly(2));Este ejemplo muestra cómo verificar que un mensaje se registró con nivel
Debug
por lo menos una vez.var loggerMock = new Mock<ILogger<MyService>>();loggerMock.VerifyLogging("My Debug Message", LogLevel.Debug, Times.AtLeastOnce());
VerifyLoggerWasCalled
El método VerifyLoggerWasCalled
verifica que el logger fue llamado al menos una vez.
public static void VerifyLoggerWasCalled<T>(this Mock<ILogger<T>> logger)
-
Parámetros:
logger
: El mock delILogger<T>
que se verificará.
-
Tipo de retorno:
void
: No retorna nada, pero lanza una excepción si la verificación no coincide.
-
Ejemplos de código:
Este ejemplo muestra cómo verificar que el logger fue llamado al menos una vez.
// Ejemplo 1: Verificar que el logger fue llamado al menos una vezvar loggerMock = new Mock<ILogger<MyService>>();//... código donde se llama el loggerloggerMock.VerifyLoggerWasCalled();Este ejemplo muestra cómo verificar que el logger no fue llamado.
// Ejemplo 2: Verificar que el logger no fue llamadovar loggerMock = new Mock<ILogger<MyService>>();//... código que no llama al loggerAssert.Throws<MockException>(() => loggerMock.VerifyLoggerWasCalled()); //Lanza excepcion por que no se llamo el logger
Conclusiones
- La clase
LoggerExtensions
simplifica la verificación de logging en pruebas unitarias usando mocks deILogger
. - Los métodos de extensión
VerifyLogging
yVerifyLoggerWasCalled
hacen que las pruebas sean más claras y concisas. - Permiten verificar el mensaje, el nivel de log y la cantidad de veces que se llama el logger.
- Es importante usar esta herramienta para asegurar que los componentes registren información relevante para el debugging y monitoreo.
- Asegúrate de configurar correctamente los mocks antes de realizar las verificaciones.