Skip to content

xUnit Logger

Este conjunto de clases y extensiones proporciona una implementación de logging para pruebas unitarias con xUnit, que permite dirigir los mensajes de log directamente a la salida de la prueba. Esto facilita la depuración y el seguimiento del comportamiento de las aplicaciones durante las pruebas.

namespace CodeDesignPlus.Net.xUnit.Output.Loggers;
/// <summary>
/// Provides extension methods for xUnit logging.
/// </summary>
public static class XunitExtensions
{
/// <summary>
/// Determines whether the logging builder uses scopes.
/// </summary>
/// <param name="builder">The logging builder.</param>
/// <returns><c>true</c> if the logging builder uses scopes; otherwise, <c>false</c>.</returns>
public static bool UsesScopes(this ILoggingBuilder builder)
{
var serviceProvider = builder.Services.BuildServiceProvider();
// Look for other host builders on this chain calling ConfigureLogging explicitly
var options = serviceProvider.GetService<SimpleConsoleFormatterOptions>() ??
serviceProvider.GetService<JsonConsoleFormatterOptions>() ??
serviceProvider.GetService<ConsoleFormatterOptions>();
if (options != default)
return options.IncludeScopes;
// Look for other configuration sources
// See: https://docs.microsoft.com/en-us/dotnet/core/extensions/logging?tabs=command-line#set-log-level-by-command-line-environment-variables-and-other-configuration
var config = serviceProvider.GetService<IConfigurationRoot>() ?? serviceProvider.GetService<IConfiguration>();
var logging = config?.GetSection("Logging");
if (logging == default)
return false;
var includeScopes = logging?.GetValue("Console:IncludeScopes", false);
if (!includeScopes.Value)
includeScopes = logging?.GetValue("IncludeScopes", false);
return includeScopes.GetValueOrDefault(false);
}
}

Dependencias y Requisitos Previos


  • Microsoft.Extensions.Logging: Necesario para las interfaces y enumeraciones de logging (ILogger, ILoggerProvider, ILoggingBuilder, LogLevel, EventId, IExternalScopeProvider).
  • Microsoft.Extensions.DependencyInjection: Necesario para usar IServiceCollection y ServiceProvider.
  • Microsoft.Extensions.Configuration: Necesario para la interfaz IConfiguration y para leer configuración
  • xunit: Necesario para ITestOutputHelper.
  • System: Necesario para Func, StringBuilder, IDisposable, etc.

Escenarios de uso


Este conjunto de clases es particularmente útil en pruebas unitarias (xUnit) donde se necesita:

  • Capturar mensajes de log generados durante la ejecución de las pruebas.
  • Verificar que los componentes generan los logs esperados.
  • Examinar los logs de forma sencilla y directa en la salida de las pruebas.
  • Integrar el logging de la aplicación con las pruebas unitarias sin necesidad de configuraciones complejas.
  • Probar componentes de la capa de infraestructura que utilizan logging.

Beneficios


  • Integración sencilla: Se integra fácilmente con las pruebas xUnit al implementar ILogger y ILoggerProvider.
  • Salida directa: Los mensajes de log se escriben directamente en la salida de la prueba, lo que facilita la lectura y depuración.
  • Soporte de scopes: Admite el uso de scopes para organizar los mensajes de log.
  • Verificación directa: Permite verificar la lógica del logger en las pruebas.
  • Personalizable: Posibilidad de ajustar el comportamiento del logger a través de la configuración.

Ejemplo Práctico


Este ejemplo muestra cómo configurar y usar el XunitLoggerExtensions en una prueba unitaria:

using Xunit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using CodeDesignPlus.Net.xUnit.Helpers.Loggers;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
public class LoggingTests
{
private readonly ITestOutputHelper _output;
public LoggingTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void MyService_LogsMessages_Successfully()
{
// Arrange
var host = Host.CreateDefaultBuilder()
.ConfigureLogging((loggingBuilder) =>
{
loggingBuilder.AddProvider(new XunitLoggerProvider(_output,loggingBuilder.UsesScopes()));
})
.Build();
var logger = host.Services.GetRequiredService<ILogger<MyService>>();
var service = new MyService(logger);
// Act
service.DoSomething();
using (_logger.BeginScope("my_scope_1"))
{
_logger.LogInformation("My message log with scope");
using (_logger.BeginScope("my_scope_2"))
{
_logger.LogWarning("My message log with scope 2");
}
}
}
}

En este ejemplo:

  1. Se crea un HostBuilder con la configuración básica de logging.
  2. Se agrega el proveedor XunitLoggerProvider para redireccionar los logs a la salida de xUnit.
  3. Se crea un logger de tipo ILogger<MyService>.
  4. Se instancia la clase MyService con el logger creado.
  5. Se ejecuta la acción DoSomething de la clase MyService que escribe diferentes tipos de logs.
  6. Se crea un scope y se realiza un log de información
  7. Se crea otro scope anidado y se realiza un log de warning
  8. La salida del test en xUnit reflejará todos los logs generados

Métodos de extensión


La clase XunitLoggerExtensions proporciona un método de extensión llamado UsesScopes que permite determinar si un ILoggingBuilder utiliza scopes. A continuación se detallan los aspectos clave de este método:

UsesScopes

El método UsesScopes permite determinar si un ILoggingBuilder utiliza scopes.

public static bool UsesScopes(this ILoggingBuilder builder)
  • Parámetros:
    • builder: El ILoggingBuilder que se va a analizar.
  • Tipo de retorno:
    • bool: Indica si el ILoggingBuilder utiliza scopes (true) o no (false).
  • Ejemplos de código:
    var host = Host.CreateDefaultBuilder()
    .ConfigureLogging((loggingBuilder) =>
    {
    var usesScopes = loggingBuilder.UsesScopes();
    //...
    })
    .Build();

Clases


La librería CodeDesignPlus.Net.xUnit.Helpers.Loggers proporciona las siguientes clases para la implementación de logging en pruebas unitarias:

XunitLogger

Implementación de ILogger que escribe los logs a la salida de xUnit.

  • Constructores:

    public XunitLogger(ITestOutputHelper output, IExternalScopeProvider scopes, string categoryName, bool useScopes)
    • output: ITestOutputHelper para escribir a la salida de la prueba.
    • scopes: El proveedor de scopes.
    • categoryName: Nombre de la categoría del logger.
    • useScopes: Indica si se usan scopes.
  • Métodos:

    • IsEnabled(LogLevel logLevel): Indica si un nivel de log está habilitado.
    • BeginScope<TState>(TState state): Inicia un scope.
    • Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter): Escribe el log a la salida, con la información formateada.

XunitLoggerProvider

Implementación de ILoggerProvider que crea instancias de XunitLogger.

  • Constructores:
    public XunitLoggerProvider(ITestOutputHelper output, bool useScopes)
    • output: ITestOutputHelper para escribir a la salida de la prueba.
    • useScopes: Indica si se usan scopes.
  • Métodos:
    • CreateLogger(string categoryName): Crea una instancia de XunitLogger.
    • Dispose(): Elimina el proveedor de logs.
    • SetScopeProvider(IExternalScopeProvider scopes): Asigna el proveedor de scopes.

Conclusiones


  • Las clases XunitLogger, XunitLoggerProvider y la extensión UsesScopes permiten una integración sencilla del logging en las pruebas unitarias de xUnit.
  • Los logs se escriben directamente a la salida de xUnit, lo que facilita la depuración.
  • Se admite el uso de scopes para organizar los mensajes de log.
  • La configuración del logger se realiza de manera flexible mediante la implementación de ILoggerProvider.
  • Es importante utilizar UsesScopes para determinar si se debe usar los scopes.

Referencias