### Complete Unit Test Example for Payment Service Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt This example demonstrates how to test a service class that uses Serilog for logging. It includes setup for the in-memory sink, service instantiation, method calls, and assertions to verify log messages, levels, and properties. ```csharp using Serilog; using Serilog.Events; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; using Xunit; public class PaymentService { private readonly ILogger _logger; public PaymentService(ILogger logger) { _logger = logger; } public bool ProcessPayment(string orderId, decimal amount) { _logger.Information("Processing payment for order {OrderId} with amount {Amount}", orderId, amount); if (amount <= 0) { _logger.Error("Invalid payment amount {Amount} for order {OrderId}", amount, orderId); return false; } if (amount > 10000) { _logger.Warning("Large payment {Amount} for order {OrderId} requires review", amount, orderId); } _logger.Information("Payment completed for order {OrderId}", orderId); return true; } } public class PaymentServiceTests { [Fact] public void ProcessPayment_WithValidAmount_LogsProcessingAndCompletion() { var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); var service = new PaymentService(logger); var result = service.ProcessPayment("ORD-001", 99.99m); Assert.True(result); InMemorySink.Instance .Should() .HaveMessage("Processing payment for order {OrderId} with amount {Amount}") .Appearing().Once() .WithProperty("OrderId") .WithValue("ORD-001") .WithProperty("Amount") .WithValue(99.99m); InMemorySink.Instance .Should() .HaveMessage("Payment completed for order {OrderId}") .Appearing().Once() .WithLevel(LogEventLevel.Information); } [Fact] public void ProcessPayment_WithInvalidAmount_LogsError() { var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); var service = new PaymentService(logger); var result = service.ProcessPayment("ORD-002", -50m); Assert.False(result); InMemorySink.Instance .Should() .HaveMessage("Invalid payment amount {Amount} for order {OrderId}") .Appearing().Once() .WithLevel(LogEventLevel.Error); InMemorySink.Instance .Should() .NotHaveMessage("Payment completed for order {OrderId}"); } [Fact] public void ProcessPayment_WithLargeAmount_LogsWarning() { var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); var service = new PaymentService(logger); var result = service.ProcessPayment("ORD-003", 15000m); Assert.True(result); InMemorySink.Instance .Should() .HaveMessage("Large payment {Amount} for order {OrderId} requires review") .Appearing().Once() .WithLevel(LogEventLevel.Warning) .WithProperty("Amount") .WithValue(15000m); } } ``` -------------------------------- ### Install Serilog.Sinks.InMemory via CLI Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Commands to add the base in-memory sink package to your project. ```bash dotnet add package Serilog.Sinks.InMemory ``` ```PowerShell Install-Package Serilog.Sinks.InMemory ``` -------------------------------- ### Install Serilog.Sinks.InMemory.Assertions via CLI Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Commands to add the assertions package, which provides FluentAssertions support for testing logs. ```bash dotnet add package Serilog.Sinks.InMemory.Assertions ``` ```PowerShell Install-Package Serilog.Sinks.InMemory.Assertions ``` -------------------------------- ### Define Business Logic Class Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Example class structure that accepts an ILogger for dependency injection. ```csharp public class ComplicatedBusinessLogic { private readonly ILogger _logger; public ComplicatedBusinessLogic(ILogger logger) { _logger = logger; } public string FirstTenCharacters(string input) { return input.Substring(0, 10); } } ``` -------------------------------- ### Test Log Message Existence Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Basic test setup using the in-memory sink to verify a specific log message template was recorded. ```csharp public class WhenExecutingBusinessLogic { public void GivenInputOfFiveCharacters_MessageIsLogged() { var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); var logic = new ComplicatedBusinessLogic(logger); logic.FirstTenCharacters("12345"); // Use the static Instance property to access the in-memory sink InMemorySink.Instance .Should() .HaveMessage("Input is {count} characters long"); } } ``` -------------------------------- ### Implement Logging in Business Logic Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Updated method implementation to include a Serilog information log. ```csharp public string FirstTenCharacters(string input) { _logger.Information("Input is {count} characters long", input.Length); return input.Substring(0, 10); } ``` -------------------------------- ### Configuring the InMemory logger Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Create a basic logger instance using LoggerConfiguration. ```csharp var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); ``` -------------------------------- ### Configure Basic InMemory Sink Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Configure Serilog to use the InMemory sink. The sink captures all log events in memory and provides access via the static Instance property. This is useful for basic logging during tests. ```csharp using Serilog; using Serilog.Events; using Serilog.Sinks.InMemory; // Basic configuration var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); logger.Information("Application started"); logger.Warning("Configuration missing for {Setting}", "EmailServer"); logger.Error("Failed to connect to database"); // Access logged events through the static Instance property foreach (var logEvent in InMemorySink.Instance.LogEvents) { Console.WriteLine($"[{logEvent.Level}] {logEvent.MessageTemplate.Text}"); } // Output: // [Information] Application started // [Warning] Configuration missing for {Setting} // [Error] Failed to connect to database ``` -------------------------------- ### Setting output templates Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Customize the log format using the outputTemplate parameter. ```csharp var logger = new LoggerConfiguration() .WriteTo.InMemory(outputTemplate: "{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") .CreateLogger(); ``` -------------------------------- ### Managing dynamic log levels Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Use LoggingLevelSwitch to change the minimum log level at runtime. ```csharp var levelSwitch = new LoggingLevelSwitch(); ``` ```csharp levelSwitch.MinimumLevel = LogEventLevel.Warning; ``` ```csharp var log = new LoggerConfiguration() .MinimumLevel.ControlledBy(levelSwitch) .WriteTo.InMemory() .CreateLogger(); ``` ```csharp levelSwitch.MinimumLevel = LogEventLevel.Verbose; log.Verbose("This will now be logged"); ``` -------------------------------- ### Create and Use Sink Snapshots for Assertions Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Generate a thread-safe snapshot of current log events for assertions. This prevents race conditions when the logger is accessed from multiple threads. The snapshot remains unchanged even if more messages are logged to the original sink afterward. ```csharp using Serilog; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; var inMemorySink = new InMemorySink(); var logger = new LoggerConfiguration() .WriteTo.Sink(inMemorySink) .CreateLogger(); logger.Information("First message"); logger.Warning("Second message"); // Create a snapshot for thread-safe assertions var snapshot = inMemorySink.Snapshot(); // Even if more messages are logged now, the snapshot remains unchanged logger.Error("Third message - not in snapshot"); // Assert against the snapshot snapshot .Should() .HaveMessage("First message") .Appearing().Once(); snapshot .Should() .HaveMessage("Second message") .Appearing().Once(); // The snapshot only has 2 messages Console.WriteLine($"Snapshot has {snapshot.LogEvents.Count()} events"); // Output: Snapshot has 2 events // The original sink has 3 messages Console.WriteLine($"Original sink has {inMemorySink.LogEvents.Count()} events"); // Output: Original sink has 3 events ``` -------------------------------- ### Dynamic Level Switching with InMemory Sink Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Use LoggingLevelSwitch to dynamically change the minimum log level at runtime for the InMemory sink. This is useful for enabling verbose logging during debugging without reconfiguring the logger. ```csharp using Serilog; using Serilog.Core; using Serilog.Events; using Serilog.Sinks.InMemory; var levelSwitch = new LoggingLevelSwitch(); levelSwitch.MinimumLevel = LogEventLevel.Warning; var logger = new LoggerConfiguration() .MinimumLevel.ControlledBy(levelSwitch) .WriteTo.InMemory() .CreateLogger(); logger.Information("Not logged - below Warning level"); logger.Warning("Logged at Warning level"); // Dynamically lower the minimum level levelSwitch.MinimumLevel = LogEventLevel.Verbose; logger.Verbose("Now this verbose message is logged"); logger.Debug("And this debug message too"); ``` -------------------------------- ### Use Custom LogEvent Predicate Matching Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Employ the `Match` method for sophisticated custom assertions on log events. This allows for complex predicate logic to be applied when verifying log entries. ```csharp using Serilog; using Serilog.Events; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); logger.Information("User {UserId} performed action {Action} at {Timestamp}", "user-123", "LOGIN", DateTimeOffset.UtcNow); // Use Match for complex custom predicates InMemorySink.Instance .Should() .HaveMessage("User {UserId} performed action {Action} at {Timestamp}") .Appearing().Once() .Match(logEvent => logEvent.Level == LogEventLevel.Information && logEvent.Properties.ContainsKey("UserId") && logEvent.Properties.ContainsKey("Action")); ``` -------------------------------- ### Asserting With Log Context Properties Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Verify properties added through Serilog's LogContext enrichment. This allows you to assert on contextual information that is automatically added to your log messages. ```csharp using Serilog; using Serilog.Context; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; var logger = new LoggerConfiguration() .Enrich.FromLogContext() .WriteTo.InMemory() .CreateLogger(); // Add properties via LogContext using (LogContext.PushProperty("CorrelationId", "abc-123")) using (LogContext.PushProperty("UserId", "user-456")) { logger.Information("Processing request for {Resource}", "/api/orders"); } // Assert both template properties and context properties InMemorySink.Instance .Should() .HaveMessage("Processing request for {Resource}") .Appearing().Once() .WithProperty("Resource") .WithValue("/api/orders") .WithProperty("CorrelationId") .WithValue("abc-123") .WithProperty("UserId") .WithValue("user-456"); ``` -------------------------------- ### Verify Log Frequency Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Assert that a specific log message appears exactly once. ```csharp public void GivenInputOfFiveCharacters_MessageIsLoggedOnce() { /* omitted for brevity */ InMemorySink.Instance .Should() .HaveMessage("Input is {count} characters long") .Appearing().Once(); } ``` -------------------------------- ### Setting minimum log levels Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Restrict the sink to specific log levels using the restrictedToMinimumLevel parameter. ```csharp var logger = new LoggerConfiguration() .WriteTo.InMemory(restrictedToMinimumLevel: Events.LogEventLevel.Information) .CreateLogger(); ``` -------------------------------- ### Configure Minimum Log Level for InMemory Sink Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Filter log events by specifying a minimum log level when configuring the InMemory sink. Events below this level will not be captured. This helps reduce noise in test logs. ```csharp using Serilog; using Serilog.Events; using Serilog.Sinks.InMemory; // Only capture Information level and above var logger = new LoggerConfiguration() .WriteTo.InMemory(restrictedToMinimumLevel: LogEventLevel.Information) .CreateLogger(); logger.Verbose("This verbose message is filtered out"); logger.Debug("This debug message is filtered out"); logger.Information("This information message is captured"); logger.Warning("This warning message is captured"); // Only 2 events captured (Information and Warning) Console.WriteLine($"Captured events: {InMemorySink.Instance.LogEvents.Count()}"); // Output: Captured events: 2 ``` -------------------------------- ### Assert Log Property Exists and Has Value Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Verify that structured log properties are present and contain expected values. The logger must be configured with InMemory sink. ```csharp using Serilog; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); logger.Information("Order {OrderId} placed for customer {CustomerId} with total {Total}", 12345, "CUST-001", 99.99m); // Assert property exists InMemorySink.Instance .Should() .HaveMessage("Order {OrderId} placed for customer {CustomerId} with total {Total}") .Appearing().Once() .WithProperty("OrderId"); // Assert property has specific value InMemorySink.Instance .Should() .HaveMessage("Order {OrderId} placed for customer {CustomerId} with total {Total}") .Appearing().Once() .WithProperty("OrderId") .WithValue(12345); // Assert multiple properties using And InMemorySink.Instance .Should() .HaveMessage("Order {OrderId} placed for customer {CustomerId} with total {Total}") .Appearing().Once() .WithProperty("OrderId") .WithValue(12345) .WithProperty("CustomerId") .WithValue("CUST-001"); ``` -------------------------------- ### Assert Log Event Level Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Verify that log messages are written at the expected severity level. Ensure the logger is configured with InMemory sink. ```csharp using Serilog; using Serilog.Events; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); logger.Error("Database connection failed for server {ServerName}", "PROD-DB-01"); // Assert the message was logged at Error level InMemorySink.Instance .Should() .HaveMessage("Database connection failed for server {ServerName}") .Appearing().Once() .WithLevel(LogEventLevel.Error); ``` -------------------------------- ### Pattern Matching with Containing Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Match log messages using partial text patterns instead of exact message templates. This is useful when the exact log message may vary slightly. Ensure the message appears exactly once. ```csharp using Serilog; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); logger.Information("Starting database migration for schema version 5"); logger.Warning("Connection timeout after 30 seconds"); logger.Error("Critical failure in payment processing module"); // Match messages containing a pattern InMemorySink.Instance .Should() .HaveMessage() .Containing("database migration") .Appearing().Once(); InMemorySink.Instance .Should() .HaveMessage() .Containing("timeout") .Appearing().Once(); InMemorySink.Instance .Should() .HaveMessage() .Containing("payment") .Appearing().Once(); ``` -------------------------------- ### Assert Specific Message Logged with InMemory Sink Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Use the fluent assertion API provided by Serilog.Sinks.InMemory.Assertions to verify that a specific log message template was written to the sink. Ensure the correct message template is used for assertion. ```csharp using Serilog; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); logger.Information("User {Username} logged in from {IpAddress}", "john_doe", "192.168.1.1"); // Assert that the message was logged InMemorySink.Instance .Should() .HaveMessage("User {Username} logged in from {IpAddress}"); // Fails with: Expected a message to be logged with template "User logged in" but didn't find any // InMemorySink.Instance // .Should() // .HaveMessage("User logged in"); ``` -------------------------------- ### Verify Multiple Log Occurrences Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Assert that a log message appears a specific number of times. ```csharp public void GivenLoopWithFiveItems_MessageIsLoggedFiveTimes() { /* omitted for brevity */ InMemorySink.Instance .Should() .HaveMessage("Input is {count} characters long") .Appearing().Times(5); } ``` -------------------------------- ### Assert log level with WithLevel Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Verifies that a logged message matches a specific LogEventLevel. ```csharp public void GivenLoopWithFiveItems_MessageIsLoggedFiveTimes() { /* omitted for brevity */ InMemorySink.Instance .Should() .HaveMessage("Input is {count} characters long") .Appearing().Once() .WithLevel(LogEventLevel.Information); } ``` ```csharp public void GivenLoopWithFiveItems_MessageIsLoggedFiveTimes() { logger.Warning("Test message"); logger.Warning("Test message"); logger.Warning("Test message"); InMemorySink.Instance .Should() .HaveMessage("Test message") .Appearing().Times(3) .WithLevel(LogEventLevel.Information); } ``` -------------------------------- ### Asserting No Messages Were Logged Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Verify that no messages were logged at all, or assert that a specific message was not logged. This is useful for testing methods that should not produce logs under normal conditions. ```csharp using Serilog; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); // Test a method that should not produce any logs under normal conditions // (no logging performed) // Assert no messages were logged at all InMemorySink.Instance .Should() .NotHaveMessage(); // Now log something logger.Information("Application started"); // Assert a specific message was not logged InMemorySink.Instance .Should() .NotHaveMessage("Error occurred"); // Assert another specific message was not logged InMemorySink.Instance .Should() .NotHaveMessage("Application crashed"); ``` -------------------------------- ### Assert log properties and values Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Validates the presence and values of properties attached to log messages. ```csharp InMemorySink.Instance .Should() .HaveMessage("Message with {Property}") .Appearing().Once() .WithProperty("Property"); ``` ```csharp InMemorySink.Instance .Should() .HaveMessage("Message with {Property}") .Appearing().Once() .WithProperty("Property") .WithValue("property value"); ``` ```csharp InMemorySink.Instance .Should() .HaveMessage("Message with {Property1} and {Property2}") .Appearing().Once() .WithProperty("Property1") .WithValue("value 1") .And .WithProperty("Property2") .WithValue("value 2"); ``` ```csharp InMemorySink.Instance .Should() .HaveMessage("Message with {Property1} and {Property2}") .Appearing().Times(3) .WithProperty("Property1") .WithValue("value 1", "value 2", "value 3") ``` -------------------------------- ### Verify Log Property Value Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Assert that a log message contains a specific property with an expected value. ```csharp public void GivenInputOfFiveCharacters_CountPropertyValueIsFive() { /* omitted for brevity */ InMemorySink.Instance .Should() .HaveMessage("Input is {count} characters long") .Appearing().Once() .WithProperty("count") .WithValue(5); } ``` -------------------------------- ### Assert Property Values Across Multiple Messages Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt When a message appears multiple times with different property values, verify all expected values are present. Ensure logger is configured with InMemory sink. ```csharp using Serilog; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); // Log multiple events with same template but different values logger.Information("Processing file {FileName}", "report_q1.csv"); logger.Information("Processing file {FileName}", "report_q2.csv"); logger.Information("Processing file {FileName}", "report_q3.csv"); // Assert all three files were processed InMemorySink.Instance .Should() .HaveMessage("Processing file {FileName}") .Appearing().Times(3) .WithProperty("FileName") .WithValues("report_q1.csv", "report_q2.csv", "report_q3.csv"); ``` -------------------------------- ### Asserting on Destructured Objects Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Use the '@' prefix to destructure objects when logging. Assert on the properties of these destructured objects using the assertion library. Ensure the object is logged exactly once. ```csharp using Serilog; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; public class OrderDetails { public int OrderId { get; set; } public string CustomerName { get; set; } public decimal TotalAmount { get; set; } } var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); var order = new OrderDetails { OrderId = 12345, CustomerName = "John Smith", TotalAmount = 199.99m }; // Use @ prefix to destructure the object logger.Information("Order placed: {@Order}", order); // Assert on properties of the destructured object InMemorySink.Instance .Should() .HaveMessage("Order placed: {@Order}") .Appearing().Once() .WithProperty("Order") .HavingADestructuredObject() .WithProperty("CustomerName") .WithValue("John Smith"); // Chain multiple destructured property assertions InMemorySink.Instance .Should() .HaveMessage("Order placed: {@Order}") .Appearing().Once() .WithProperty("Order") .HavingADestructuredObject() .WithProperty("OrderId") .WithValue(12345); ``` -------------------------------- ### Assert message patterns with Containing Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Matches log messages that contain a specific substring pattern. ```csharp InMemorySink.Instance .Should() .HaveMessage() .Containing("some pattern") .Appearing().Once(); ``` -------------------------------- ### Clear Log Events Between Tests with IDisposable Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Utilize the IDisposable pattern to ensure log events are cleared between tests, which is crucial for test frameworks like MSTest that reuse test class instances. The Initialize method disposes of the previous logger, effectively clearing the InMemorySink. ```csharp using Microsoft.VisualStudio.TestTools.UnitTesting; using Serilog; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; [TestClass] public class OrderServiceTests { private Logger _logger; [TestInitialize] public void Initialize() { // Dispose previous logger to clear the InMemorySink _logger?.Dispose(); _logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); } [TestMethod] public void PlaceOrder_LogsOrderPlaced() { _logger.Information("Order {OrderId} placed", 100); InMemorySink.Instance .Should() .HaveMessage("Order {OrderId} placed") .Appearing().Once(); } [TestMethod] public void CancelOrder_LogsOrderCancelled() { // This test won't see logs from PlaceOrder_LogsOrderPlaced // because Initialize() disposes and recreates the logger _logger.Information("Order {OrderId} cancelled", 200); InMemorySink.Instance .Should() .HaveMessage("Order {OrderId} cancelled") .Appearing().Once(); } } ``` -------------------------------- ### Assert Message Occurrence Count with InMemory Sink Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Verify the number of times a specific log message template appears in the InMemory sink using the assertion API. Supports asserting exact counts, once, or any number of times. ```csharp using Serilog; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); // Log the same message template multiple times for (int i = 0; i < 5; i++) { logger.Information("Processing item {ItemNumber}", i); } // Assert it appears exactly 5 times InMemorySink.Instance .Should() .HaveMessage("Processing item {ItemNumber}") .Appearing().Times(5); // Assert a different message appears exactly once logger.Information("Batch processing complete"); InMemorySink.Instance .Should() .HaveMessage("Batch processing complete") .Appearing().Once(); ``` -------------------------------- ### Assert message occurrence counts Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Verifies the frequency of log messages or asserts that specific messages were not logged. ```csharp InMemorySink.Instance .Should() .HaveMessage() .Appearing().Times(3); // Expect three messages to be logged ``` ```csharp InMemorySink.Instance .Should() .NotHaveMessage(); ``` ```csharp InMemorySink.Instance .Should() .NotHaveMessage("a specific message"); ``` -------------------------------- ### Clearing log events in MSTest Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Implement IDisposable to clear the InMemorySink log events between test runs to prevent cross-test interference. ```csharp [TestClass] public class WhenDemonstratingDisposableFeature { private Logger _logger; [TestInitialize] public void Initialize() { _logger?.Dispose(); _logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); } [TestMethod] public void GivenAFoo_BarIsBlah() { _logger.Information("Foo"); InMemorySink.Instance .Should() .HaveMessage("Foo"); } [TestMethod] public void GivenABar_BazIsQuux() { _logger.Information("Bar"); InMemorySink.Instance .Should() .HaveMessage("Bar"); } } ``` -------------------------------- ### Access property values with WhichValue Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Accesses a log property value for advanced assertions like length or range checks. ```csharp InMemorySink.Instance .Should() .HaveMessage() .Appearing().Once() .WithProperty("PropertyOne") .WhichValue() .Should() .HaveLength(3); ``` -------------------------------- ### Using WhichValue for Advanced Property Assertions Source: https://context7.com/serilog-contrib/serilogsinksinmemory/llms.txt Access the typed property value to use native FluentAssertions or other framework assertions. Requires logger to be configured with InMemory sink and FluentAssertions. ```csharp using Serilog; using Serilog.Sinks.InMemory; using Serilog.Sinks.InMemory.Assertions; using FluentAssertions; var logger = new LoggerConfiguration() .WriteTo.InMemory() .CreateLogger(); logger.Information("Request processed in {ElapsedMs} milliseconds", 150); logger.Information("User email is {Email}", "user@example.com"); // Use WhichValue to get typed access for FluentAssertions InMemorySink.Instance .Should() .HaveMessage("Request processed in {ElapsedMs} milliseconds") .Appearing().Once() .WithProperty("ElapsedMs") .WhichValue() .Should() .BeLessThanOrEqualTo(500); InMemorySink.Instance .Should() .HaveMessage("User email is {Email}") .Appearing().Once() .WithProperty("Email") .WhichValue() .Should() .Contain("@") .And .HaveLength(16); ``` -------------------------------- ### Asserting a destructured object property Source: https://github.com/serilog-contrib/serilogsinksinmemory/blob/main/README.md Use object destructuring in log messages and verify nested properties using the HavingADestructuredObject assertion. ```csharp var someObject = new { Foo = "bar", Baz = "quux" }; logger.Information("Hello {@SomeObject}", someObject); ``` ```csharp InMemorySink.Instance .Should() .HaveMessage("Hello {@SomeObject}") .Appearing().Once() .WithProperty("SomeObject") .HavingADestructuredObject() .WithProperty("Foo") .WithValue("bar"); ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.