Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/LayeredCraft.StructuredLogging/Testing/TestingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,19 @@ public class LogEntry
public DateTimeOffset Timestamp { get; set; }
}

/// <summary>
/// A generic test implementation of ILogger&lt;T&gt; that captures log entries in memory for testing purposes.
/// This allows unit tests to verify logging behavior for strongly-typed loggers.
/// </summary>
/// <typeparam name="T">The type being logged for.</typeparam>
public class TestLogger<T> : TestLogger, ILogger<T>
{
/// <summary>
/// Initializes a new instance of the TestLogger&lt;T&gt; class.
/// </summary>
public TestLogger() : base() { }
}

/// <summary>
/// Internal implementation of a logging scope for testing purposes.
/// Tracks the state object but performs no actual scope management.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using Microsoft.Extensions.Logging;
using LayeredCraft.StructuredLogging.Testing;

namespace LayeredCraft.StructuredLogging.Tests.Testing;

public class GenericTestLoggerTests
{
private class TestService
{
}

[Fact]
public void TestLogger_T_ImplementsILogger_T()
{
var logger = new TestLogger<TestService>();

logger.Should().BeAssignableTo<ILogger<TestService>>();
logger.Should().BeAssignableTo<TestLogger>();
}

[Fact]
public void TestLogger_T_CanLogMessages()
{
var logger = new TestLogger<TestService>();

logger.LogInformation("Test message");

logger.LogEntries.Should().HaveCount(1);
logger.LogEntries[0].LogLevel.Should().Be(LogLevel.Information);
logger.LogEntries[0].FormattedMessage.Should().Be("Test message");
}

[Fact]
public void TestLogger_T_InheritsExtensionMethods()
{
var logger = new TestLogger<TestService>();

logger.LogInformation("Test message");
logger.LogError("Error message");

var lastEntry = logger.GetLastLogEntry();
lastEntry.Should().NotBeNull();
lastEntry.LogLevel.Should().Be(LogLevel.Error);
lastEntry.FormattedMessage.Should().Be("Error message");

var errorEntries = logger.GetLogEntries(LogLevel.Error);
errorEntries.Should().ContainSingle();

logger.HasLogEntry(LogLevel.Information, "Test message").Should().BeTrue();
logger.HasLogEntry(LogLevel.Error, "Error message").Should().BeTrue();

logger.LogEntries.Should().HaveCount(2);
logger.GetLogEntries(LogLevel.Information).Should().ContainSingle();
logger.GetLogEntries(LogLevel.Error).Should().ContainSingle();
}

[Fact]
public void TestLogger_T_CanClearEntries()
{
var logger = new TestLogger<TestService>();

logger.LogInformation("Test message");
logger.LogEntries.Should().ContainSingle();

logger.Clear();
logger.LogEntries.Should().BeEmpty();
}

[Fact]
public void TestLogger_T_SharedLogEntriesWithBase()
{
var logger = new TestLogger<TestService>();
TestLogger baseLogger = logger;

logger.LogInformation("From generic logger");
baseLogger.LogError("From base logger");

logger.LogEntries.Should().HaveCount(2);
baseLogger.LogEntries.Should().HaveCount(2);
logger.LogEntries.Should().BeSameAs(baseLogger.LogEntries);
}

[Fact]
public void TestLogger_T_CanBeUsedWhereILogger_T_IsExpected()
{
var logger = new TestLogger<TestService>();

// This method expects ILogger<TestService>
LogWithGenericLogger(logger);

logger.LogEntries.Should().ContainSingle();
logger.LogEntries[0].FormattedMessage.Should().Be("Generic logger test");
logger.LogEntries[0].LogLevel.Should().Be(LogLevel.Warning);
}

private static void LogWithGenericLogger(ILogger<TestService> logger)
{
logger.LogWarning("Generic logger test");
}
}
Loading