From 561b3f6ecfb0fe6c92a7eb1f85c6bf9927be80a6 Mon Sep 17 00:00:00 2001 From: VasiliyF Date: Wed, 22 Oct 2025 22:27:40 +0200 Subject: [PATCH 1/4] Fix README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fac5a31..772e7b5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Serilog.Sinks.InMemory -In-memory sink for Serilog to use for testing with [FluentAssertions](https://fluentassertions.com/) support for easy to write assertions. +In-memory sink for Serilog to use for testing with [FluentAssertions](https://fluentassertions.com/), [AwesomeAssertions](https://github.com/AwesomeAssertions/AwesomeAssertions) or [Shouldly]() support for easy to write assertions. ## Build status From 265bc8d357ac53c399db3c01b8f05a229877f004 Mon Sep 17 00:00:00 2001 From: VasiliyF Date: Wed, 22 Oct 2025 22:34:37 +0200 Subject: [PATCH 2/4] Add DebuggerTypeProxy for InMemorySink to simplify debugging --- src/Serilog.Sinks.InMemory/InMemorySink.cs | 45 +++++++++++++------ .../InMemorySinkAdvDebugProxy.cs | 28 ++++++++++++ 2 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 src/Serilog.Sinks.InMemory/InMemorySinkAdvDebugProxy.cs diff --git a/src/Serilog.Sinks.InMemory/InMemorySink.cs b/src/Serilog.Sinks.InMemory/InMemorySink.cs index e6c8ca9..dece963 100644 --- a/src/Serilog.Sinks.InMemory/InMemorySink.cs +++ b/src/Serilog.Sinks.InMemory/InMemorySink.cs @@ -1,5 +1,9 @@ -using System; +#nullable enable + +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; using System.Linq; using System.Threading; using Serilog.Core; @@ -7,14 +11,17 @@ namespace Serilog.Sinks.InMemory { + [DebuggerTypeProxy(typeof(InMemorySinkAdvDebugProxy))] public class InMemorySink : ILogEventSink, IDisposable { private static readonly AsyncLocal LocalInstance = new AsyncLocal(); + private ReadOnlyCollection? _logEventsSnapshot; private readonly List _logEvents; private readonly object _snapShotLock = new object(); - public InMemorySink() : this(new List()) + public InMemorySink() + : this(new List()) { } @@ -27,16 +34,12 @@ public static InMemorySink Instance { get { - if (LocalInstance.Value == null) - { - LocalInstance.Value = new InMemorySink(); - } - + LocalInstance.Value ??= new InMemorySink(); return LocalInstance.Value; } } - public IEnumerable LogEvents => _logEvents.AsReadOnly(); + public IEnumerable LogEvents => GetLogEvents(); public void Dispose() { @@ -48,17 +51,33 @@ public virtual void Emit(LogEvent logEvent) lock (_snapShotLock) { _logEvents.Add(logEvent); + _logEventsSnapshot = null; } } - public InMemorySink Snapshot() + private IEnumerable GetLogEvents() { - lock (_snapShotLock) + if (_logEventsSnapshot == null) { - var currentLogEvents = _logEvents.AsReadOnly().ToList(); - - return new InMemorySinkSnapshot(currentLogEvents); + lock (_snapShotLock) + { + _logEventsSnapshot ??= _logEvents.AsReadOnly(); + } } + + return _logEventsSnapshot; + } + + public InMemorySink Snapshot() + { + var currentLogEvents = GetLogEvents().ToList(); + return new InMemorySinkSnapshot(currentLogEvents); + } + + public InMemorySink Snapshot(Func predicate) + { + var currentLogEvents = GetLogEvents().Where(predicate).ToList(); + return new InMemorySinkSnapshot(currentLogEvents); } } } \ No newline at end of file diff --git a/src/Serilog.Sinks.InMemory/InMemorySinkAdvDebugProxy.cs b/src/Serilog.Sinks.InMemory/InMemorySinkAdvDebugProxy.cs new file mode 100644 index 0000000..d1533d5 --- /dev/null +++ b/src/Serilog.Sinks.InMemory/InMemorySinkAdvDebugProxy.cs @@ -0,0 +1,28 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using Serilog.Events; + +namespace Serilog.Sinks.InMemory +{ + internal sealed class InMemorySinkAdvDebugProxy(InMemorySink sink) + : List(sink.LogEvents.Select(logEvent => new DebugLogEvent(logEvent))); + + internal sealed class DebugLogEvent(LogEvent logEvent) + { + public LogEventLevel Level => logEvent.Level; + public string Message => logEvent.RenderMessage(); + + public string MessageTemplate => logEvent.MessageTemplate.ToString(); + + public IReadOnlyDictionary Properties => logEvent.Properties; + + public Exception? Exception => logEvent.Exception; + + public LogEvent OriginalLogEvent => logEvent; + + public override string ToString() => Message; + } +} \ No newline at end of file From 22337e48d24c6e1cc3f72bf87a9fdbc6d8cd181d Mon Sep 17 00:00:00 2001 From: VasiliyF Date: Wed, 22 Oct 2025 22:35:47 +0200 Subject: [PATCH 3/4] Improve assertions o be more friendly for assertions extensions --- .../InMemorySinkAssertions.cs | 23 ++- .../InMemorySinkAssertionsFactory.cs | 7 + .../LogEventPropertyValueAssertions.cs | 4 +- .../LogEventsPropertyAssertion.cs | 4 +- ...ks.InMemory.Assertions.Abstractions.csproj | 7 +- .../InMemorySinkExtensions.cs | 166 +++++++++--------- .../InMemorySinkAssertionsFactoryImpl.cs | 12 ++ .../InMemorySinkAssertionsImpl.cs | 69 ++++---- .../InMemorySinkAssertionsFactoryImpl.cs | 12 ++ .../InMemorySinkAssertionsImpl.cs | 73 ++++---- .../InMemorySinkAssertions.cs | 71 +++++--- .../InMemorySinkAssertionsFactoryImpl.cs | 12 ++ .../InMemorySinkAssertions.cs | 71 +++++--- .../InMemorySinkAssertionsFactoryImpl.cs | 12 ++ .../InMemorySinkAssertions.cs | 71 +++++--- .../InMemorySinkAssertionsFactoryImpl.cs | 12 ++ .../InMemorySinkAssertionsFactoryImpl.cs | 12 ++ .../InMemorySinkAssertionsImpl.cs | 73 ++++---- .../InMemorySinkAssertionsFactoryImpl.cs | 12 ++ .../InMemorySinkAssertionsImpl.cs | 86 +++++---- 20 files changed, 495 insertions(+), 314 deletions(-) create mode 100644 src/Serilog.Sinks.InMemory.Assertions.Abstractions/InMemorySinkAssertionsFactory.cs create mode 100644 src/Serilog.Sinks.InMemory.AwesomeAssertions8/InMemorySinkAssertionsFactoryImpl.cs create mode 100644 src/Serilog.Sinks.InMemory.AwesomeAssertions9/InMemorySinkAssertionsFactoryImpl.cs create mode 100644 src/Serilog.Sinks.InMemory.FluentAssertions5/InMemorySinkAssertionsFactoryImpl.cs create mode 100644 src/Serilog.Sinks.InMemory.FluentAssertions6/InMemorySinkAssertionsFactoryImpl.cs create mode 100644 src/Serilog.Sinks.InMemory.FluentAssertions7/InMemorySinkAssertionsFactoryImpl.cs create mode 100644 src/Serilog.Sinks.InMemory.FluentAssertions8/InMemorySinkAssertionsFactoryImpl.cs create mode 100644 src/Serilog.Sinks.InMemory.Shouldly4/InMemorySinkAssertionsFactoryImpl.cs diff --git a/src/Serilog.Sinks.InMemory.Assertions.Abstractions/InMemorySinkAssertions.cs b/src/Serilog.Sinks.InMemory.Assertions.Abstractions/InMemorySinkAssertions.cs index 348eba3..cd37584 100644 --- a/src/Serilog.Sinks.InMemory.Assertions.Abstractions/InMemorySinkAssertions.cs +++ b/src/Serilog.Sinks.InMemory.Assertions.Abstractions/InMemorySinkAssertions.cs @@ -1,7 +1,20 @@ -namespace Serilog.Sinks.InMemory.Assertions +#nullable enable + +using System; +using Serilog.Events; + +namespace Serilog.Sinks.InMemory.Assertions { public interface InMemorySinkAssertions { + InMemorySink Subject { get; } + + LogEventsAssertions HaveMessage( + Func predicate, + string? predicateErrorName = null, + string because = "", + params object[] becauseArgs); + LogEventsAssertions HaveMessage( string messageTemplate, string because = "", @@ -10,7 +23,13 @@ LogEventsAssertions HaveMessage( PatternLogEventsAssertions HaveMessage(); void NotHaveMessage( - string messageTemplate = null, + string? messageTemplate = null, + string because = "", + params object[] becauseArgs); + + void NotHaveMessage( + Func predicate, + string? predicateErrorName = null, string because = "", params object[] becauseArgs); } diff --git a/src/Serilog.Sinks.InMemory.Assertions.Abstractions/InMemorySinkAssertionsFactory.cs b/src/Serilog.Sinks.InMemory.Assertions.Abstractions/InMemorySinkAssertionsFactory.cs new file mode 100644 index 0000000..a95c806 --- /dev/null +++ b/src/Serilog.Sinks.InMemory.Assertions.Abstractions/InMemorySinkAssertionsFactory.cs @@ -0,0 +1,7 @@ +namespace Serilog.Sinks.InMemory.Assertions +{ + public interface InMemorySinkAssertionsFactory + { + InMemorySinkAssertions CreateInMemorySinkAssertions(InMemorySink snapshotInstance); + } +} \ No newline at end of file diff --git a/src/Serilog.Sinks.InMemory.Assertions.Abstractions/LogEventPropertyValueAssertions.cs b/src/Serilog.Sinks.InMemory.Assertions.Abstractions/LogEventPropertyValueAssertions.cs index 7efb0d5..4139ac5 100644 --- a/src/Serilog.Sinks.InMemory.Assertions.Abstractions/LogEventPropertyValueAssertions.cs +++ b/src/Serilog.Sinks.InMemory.Assertions.Abstractions/LogEventPropertyValueAssertions.cs @@ -1,6 +1,4 @@ -using FluentAssertions; - -namespace Serilog.Sinks.InMemory.Assertions; +namespace Serilog.Sinks.InMemory.Assertions; public interface LogEventPropertyValueAssertions { diff --git a/src/Serilog.Sinks.InMemory.Assertions.Abstractions/LogEventsPropertyAssertion.cs b/src/Serilog.Sinks.InMemory.Assertions.Abstractions/LogEventsPropertyAssertion.cs index 2aa8b4a..8414bb5 100644 --- a/src/Serilog.Sinks.InMemory.Assertions.Abstractions/LogEventsPropertyAssertion.cs +++ b/src/Serilog.Sinks.InMemory.Assertions.Abstractions/LogEventsPropertyAssertion.cs @@ -1,6 +1,4 @@ -using FluentAssertions; - -namespace Serilog.Sinks.InMemory.Assertions; +namespace Serilog.Sinks.InMemory.Assertions; public interface LogEventsPropertyAssertion { diff --git a/src/Serilog.Sinks.InMemory.Assertions.Abstractions/Serilog.Sinks.InMemory.Assertions.Abstractions.csproj b/src/Serilog.Sinks.InMemory.Assertions.Abstractions/Serilog.Sinks.InMemory.Assertions.Abstractions.csproj index e3a7560..0020202 100644 --- a/src/Serilog.Sinks.InMemory.Assertions.Abstractions/Serilog.Sinks.InMemory.Assertions.Abstractions.csproj +++ b/src/Serilog.Sinks.InMemory.Assertions.Abstractions/Serilog.Sinks.InMemory.Assertions.Abstractions.csproj @@ -7,12 +7,7 @@ - - all - - - all - + diff --git a/src/Serilog.Sinks.InMemory.Assertions/InMemorySinkExtensions.cs b/src/Serilog.Sinks.InMemory.Assertions/InMemorySinkExtensions.cs index 617fb19..31fb163 100644 --- a/src/Serilog.Sinks.InMemory.Assertions/InMemorySinkExtensions.cs +++ b/src/Serilog.Sinks.InMemory.Assertions/InMemorySinkExtensions.cs @@ -1,6 +1,6 @@ #nullable enable + using System; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -10,98 +10,97 @@ namespace Serilog.Sinks.InMemory.Assertions { public static class InMemorySinkAssertionExtensions { - private static Type? _assertionsType; - private static readonly object SyncRoot = new(); + public static InMemorySinkAssertionsFactory AssertionsFactory { get; } + + static InMemorySinkAssertionExtensions() + { + var factoryType = GetAssertionsFactoryType() ?? + throw new InvalidOperationException("Unable to load InMemorySinkAssertionsFactory"); + + AssertionsFactory = (InMemorySinkAssertionsFactory)Activator.CreateInstance(factoryType); + } public static InMemorySinkAssertions Should(this InMemorySink instance) { - if (_assertionsType == null) - { - lock (SyncRoot) - { - var assemblyLocation = - Path.GetDirectoryName(typeof(InMemorySinkAssertionExtensions).Assembly.Location); + var snapshotInstance = SnapshotOf(instance); + return AssertionsFactory.CreateInMemorySinkAssertions(snapshotInstance); + } - if (string.IsNullOrEmpty(assemblyLocation)) - { - throw new Exception($"Unable to determine path to load assemblies from"); - } + private static Type? GetAssertionsFactoryType() + { + var assemblyLocation = + Path.GetDirectoryName(typeof(InMemorySinkAssertionExtensions).Assembly.Location); - string? adapterName = null; - int? majorVersion = null; - Assembly? versionedAssembly = null; - - // Order is important here, first check the loaded assemblies before - // looking on disk because otherwise we might load FluentAssertions from disk - // while Shouldly is already loaded into the AppDomain and that's the one we - // should be using. - // That's also a guess but hey, if you mix and match assertion frameworks you - // can deal with the fall out. - if (IsFluentAssertionsAlreadyLoadedIntoDomain(out var fluentAssertionsAssembly)) - { - adapterName = "FluentAssertions"; - majorVersion = fluentAssertionsAssembly.GetName().Version.Major; - } - else if (IsAwesomeAssertionsAlreadyLoadedIntoDomain(out var awesomeAssertionsAssembly)) - { - adapterName = "AwesomeAssertions"; - majorVersion = awesomeAssertionsAssembly.GetName().Version.Major; - } - else if (IsShouldlyAlreadyLoadedIntoDomain(out var shouldlyAssembly)) - { - adapterName = "Shouldly"; - majorVersion = shouldlyAssembly.GetName().Version.Major; - } - else if (IsFluentAssertionsAvailableOnDisk(assemblyLocation, - out var fluentAssertionsOnDiskAssembly)) - { - adapterName = "FluentAssertions"; - majorVersion = fluentAssertionsOnDiskAssembly.GetName().Version.Major; - } - else if (IsAwesomeAssertionsAvailableOnDisk(assemblyLocation, - out var awesomeAssertionsOnDiskAssembly)) - { - adapterName = "AwesomeAssertions"; - majorVersion = awesomeAssertionsOnDiskAssembly.GetName().Version.Major; - } - else if (IsShouldlyAvailableOnDisk(assemblyLocation, out var shouldlyOnDiskAssembly)) - { - adapterName = "Shouldly"; - majorVersion = shouldlyOnDiskAssembly.GetName().Version.Major; - } + if (string.IsNullOrEmpty(assemblyLocation)) + { + throw new Exception($"Unable to determine path to load assemblies from"); + } - if (adapterName != null && majorVersion != null) - { - var versionedLocation = Path.Combine( - assemblyLocation, - $"Serilog.Sinks.InMemory.{adapterName}{majorVersion}.dll"); - - if (!File.Exists(versionedLocation)) - { - throw new InvalidOperationException($"Detected {adapterName} version {majorVersion} but the assertions adapter wasn't found on disk"); - } - - versionedAssembly = Assembly.LoadFile(versionedLocation); - } + string? adapterName = null; + int? majorVersion = null; + Assembly? versionedAssembly = null; + + // Order is important here, first check the loaded assemblies before + // looking on disk because otherwise we might load FluentAssertions from disk + // while Shouldly is already loaded into the AppDomain and that's the one we + // should be using. + // That's also a guess but hey, if you mix and match assertion frameworks you + // can deal with the fall out. + if (IsFluentAssertionsAlreadyLoadedIntoDomain(out var fluentAssertionsAssembly)) + { + adapterName = "FluentAssertions"; + majorVersion = fluentAssertionsAssembly.GetName().Version.Major; + } + else if (IsAwesomeAssertionsAlreadyLoadedIntoDomain(out var awesomeAssertionsAssembly)) + { + adapterName = "AwesomeAssertions"; + majorVersion = awesomeAssertionsAssembly.GetName().Version.Major; + } + else if (IsShouldlyAlreadyLoadedIntoDomain(out var shouldlyAssembly)) + { + adapterName = "Shouldly"; + majorVersion = shouldlyAssembly.GetName().Version.Major; + } + else if (IsFluentAssertionsAvailableOnDisk(assemblyLocation, + out var fluentAssertionsOnDiskAssembly)) + { + adapterName = "FluentAssertions"; + majorVersion = fluentAssertionsOnDiskAssembly.GetName().Version.Major; + } + else if (IsAwesomeAssertionsAvailableOnDisk(assemblyLocation, + out var awesomeAssertionsOnDiskAssembly)) + { + adapterName = "AwesomeAssertions"; + majorVersion = awesomeAssertionsOnDiskAssembly.GetName().Version.Major; + } + else if (IsShouldlyAvailableOnDisk(assemblyLocation, out var shouldlyOnDiskAssembly)) + { + adapterName = "Shouldly"; + majorVersion = shouldlyOnDiskAssembly.GetName().Version.Major; + } - if (versionedAssembly != null) - { - _assertionsType = versionedAssembly - .GetTypes() - .SingleOrDefault(t => t.Name == "InMemorySinkAssertionsImpl"); - } + if (adapterName != null && majorVersion != null) + { + var versionedLocation = Path.Combine( + assemblyLocation, + $"Serilog.Sinks.InMemory.{adapterName}{majorVersion}.dll"); + + if (!File.Exists(versionedLocation)) + { + throw new InvalidOperationException($"Detected {adapterName} version {majorVersion} but the assertions adapter wasn't found on disk"); } + + versionedAssembly = Assembly.LoadFile(versionedLocation); } - if (_assertionsType == null) + if (versionedAssembly != null) { - throw new InvalidOperationException("Unable to load InMemorySinkAssertions"); + return versionedAssembly + .GetTypes() + .SingleOrDefault(t => t.Name == "InMemorySinkAssertionsFactoryImpl"); } - var snapshotInstance = SnapshotOf(instance); - - return (InMemorySinkAssertions)Activator.CreateInstance( - _assertionsType, snapshotInstance); + return null; } private static bool IsFluentAssertionsAlreadyLoadedIntoDomain( @@ -161,8 +160,9 @@ private static bool IsFluentAssertionsAvailableOnDisk( assembly = Assembly.LoadFile(assemblyPath); var metadataAttributes = assembly.GetCustomAttributes().ToList(); - - if(!metadataAttributes.Any() || metadataAttributes.Any(metadata => metadata.Value.Contains("FluentAssertions", StringComparison.OrdinalIgnoreCase))) + + if (!metadataAttributes.Any() || + metadataAttributes.Any(metadata => metadata.Value.Contains("FluentAssertions", StringComparison.OrdinalIgnoreCase))) { return true; } @@ -176,7 +176,7 @@ private static bool IsAwesomeAssertionsAvailableOnDisk( string assemblyLocation, [NotNullWhen(true)] out Assembly? assembly) { - var assemblyPath = Path.Combine(assemblyLocation, "FluentAssertions.dll"); + var assemblyPath = Path.Combine(assemblyLocation, "AwesomeAssertions.dll"); if (File.Exists(assemblyPath)) { diff --git a/src/Serilog.Sinks.InMemory.AwesomeAssertions8/InMemorySinkAssertionsFactoryImpl.cs b/src/Serilog.Sinks.InMemory.AwesomeAssertions8/InMemorySinkAssertionsFactoryImpl.cs new file mode 100644 index 0000000..4442e2d --- /dev/null +++ b/src/Serilog.Sinks.InMemory.AwesomeAssertions8/InMemorySinkAssertionsFactoryImpl.cs @@ -0,0 +1,12 @@ +using Serilog.Sinks.InMemory.Assertions; + +namespace Serilog.Sinks.InMemory.AwesomeAssertions8 +{ + public sealed class InMemorySinkAssertionsFactoryImpl : InMemorySinkAssertionsFactory + { + public InMemorySinkAssertions CreateInMemorySinkAssertions(InMemorySink snapshotInstance) + { + return new InMemorySinkAssertionsImpl(snapshotInstance); + } + } +} \ No newline at end of file diff --git a/src/Serilog.Sinks.InMemory.AwesomeAssertions8/InMemorySinkAssertionsImpl.cs b/src/Serilog.Sinks.InMemory.AwesomeAssertions8/InMemorySinkAssertionsImpl.cs index 5f60f69..b5f2dc3 100644 --- a/src/Serilog.Sinks.InMemory.AwesomeAssertions8/InMemorySinkAssertionsImpl.cs +++ b/src/Serilog.Sinks.InMemory.AwesomeAssertions8/InMemorySinkAssertionsImpl.cs @@ -1,6 +1,9 @@ -using System.Linq; +#nullable enable +using System; +using System.Linq; using FluentAssertions.Execution; using FluentAssertions.Primitives; +using Serilog.Events; using Serilog.Sinks.InMemory.Assertions; namespace Serilog.Sinks.InMemory.AwesomeAssertions8 @@ -10,44 +13,32 @@ public class InMemorySinkAssertionsImpl : ReferenceTypeAssertions nameof(InMemorySink); - public LogEventsAssertions HaveMessage( - string messageTemplate, - string because = "", - params object[] becauseArgs) + public LogEventsAssertions HaveMessage(Func predicate, string? predicateErrorName = null, string because = "", params object[] becauseArgs) { + predicateErrorName ??= ""; + var matches = Subject .LogEvents - .Where(logEvent => logEvent.MessageTemplate.Text == messageTemplate) + .Where(predicate) .ToList(); CurrentAssertionChain .BecauseOf(because, becauseArgs) .ForCondition(matches.Any()) - .FailWith( - "Expected message {0} to be logged", - messageTemplate); + .FailWith("Expected message {0} to be logged", predicateErrorName); - return new LogEventsAssertionsImpl(messageTemplate, matches, CurrentAssertionChain); + return new LogEventsAssertionsImpl(predicateErrorName, matches, CurrentAssertionChain); + } + + public LogEventsAssertions HaveMessage( + string messageTemplate, + string because = "", + params object[] becauseArgs) + { + return HaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); } public PatternLogEventsAssertions HaveMessage() @@ -56,20 +47,34 @@ public PatternLogEventsAssertions HaveMessage() } public void NotHaveMessage( - string messageTemplate = null, + string? messageTemplate = null, string because = "", params object[] becauseArgs) { + if (messageTemplate != null) + { + NotHaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); + } + else + { + NotHaveMessage(null, messageTemplate, because, becauseArgs); + } + } + + public void NotHaveMessage(Func? predicate, string? predicateErrorName = null, string because = "", params object[] becauseArgs) + { + predicateErrorName ??= ""; + int count; string failureMessage; - if (messageTemplate != null) + if (predicate != null) { count = Subject - .LogEvents - .Count(logEvent => logEvent.MessageTemplate.Text == messageTemplate); + .LogEvents + .Count(predicate); - failureMessage = $"Expected message \"{messageTemplate}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; + failureMessage = $"Expected message \"{predicateErrorName}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; } else { diff --git a/src/Serilog.Sinks.InMemory.AwesomeAssertions9/InMemorySinkAssertionsFactoryImpl.cs b/src/Serilog.Sinks.InMemory.AwesomeAssertions9/InMemorySinkAssertionsFactoryImpl.cs new file mode 100644 index 0000000..b62d681 --- /dev/null +++ b/src/Serilog.Sinks.InMemory.AwesomeAssertions9/InMemorySinkAssertionsFactoryImpl.cs @@ -0,0 +1,12 @@ +using Serilog.Sinks.InMemory.Assertions; + +namespace Serilog.Sinks.InMemory.AwesomeAssertions9 +{ + public sealed class InMemorySinkAssertionsFactoryImpl : InMemorySinkAssertionsFactory + { + public InMemorySinkAssertions CreateInMemorySinkAssertions(InMemorySink snapshotInstance) + { + return new InMemorySinkAssertionsImpl(snapshotInstance); + } + } +} \ No newline at end of file diff --git a/src/Serilog.Sinks.InMemory.AwesomeAssertions9/InMemorySinkAssertionsImpl.cs b/src/Serilog.Sinks.InMemory.AwesomeAssertions9/InMemorySinkAssertionsImpl.cs index 35d68b1..802bdb7 100644 --- a/src/Serilog.Sinks.InMemory.AwesomeAssertions9/InMemorySinkAssertionsImpl.cs +++ b/src/Serilog.Sinks.InMemory.AwesomeAssertions9/InMemorySinkAssertionsImpl.cs @@ -1,53 +1,50 @@ -using System.Linq; +#nullable enable + +using System; +using System.Linq; using AwesomeAssertions.Execution; using AwesomeAssertions.Primitives; +using Serilog.Events; using Serilog.Sinks.InMemory.Assertions; namespace Serilog.Sinks.InMemory.AwesomeAssertions9 { public class InMemorySinkAssertionsImpl : ReferenceTypeAssertions, InMemorySinkAssertions { - public InMemorySinkAssertionsImpl(InMemorySink snapshotInstance) : base(snapshotInstance, AssertionChain.GetOrCreate()) - { - } - - /* - * Hack attack. - * - * This is a bit of a dirty way to work around snapshotting the InMemorySink instance - * to ensure that you won't get hit by an InvalidOperationException when calling - * HaveMessage() and the logger gets called from somewhere else and adds a new - * LogEvent to the collection while that method is invoked. - * - * For now we copy the LogEvents from the current sink and use reflection to assign - * it to a new instance of InMemorySink that will be used by the assertions, - * effectively creating a snapshot of the InMemorySink that was used by the tests. - */ - private static InMemorySink SnapshotOf(InMemorySink instance) + public InMemorySinkAssertionsImpl(InMemorySink snapshotInstance) + : base(snapshotInstance, AssertionChain.GetOrCreate()) { - return instance.Snapshot(); } protected override string Identifier => nameof(InMemorySink); public LogEventsAssertions HaveMessage( - string messageTemplate, + Func predicate, + string? predicateErrorName = null, string because = "", params object[] becauseArgs) { + predicateErrorName ??= ""; + var matches = Subject .LogEvents - .Where(logEvent => logEvent.MessageTemplate.Text == messageTemplate) + .Where(predicate) .ToList(); CurrentAssertionChain .BecauseOf(because, becauseArgs) .ForCondition(matches.Any()) - .FailWith( - "Expected message {0} to be logged", - messageTemplate); + .FailWith("Expected message {0} to be logged", predicateErrorName); - return new LogEventsAssertionsImpl(messageTemplate, matches, CurrentAssertionChain); + return new LogEventsAssertionsImpl(predicateErrorName, matches, CurrentAssertionChain); + } + + public LogEventsAssertions HaveMessage( + string messageTemplate, + string because = "", + params object[] becauseArgs) + { + return HaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); } public PatternLogEventsAssertions HaveMessage() @@ -56,20 +53,34 @@ public PatternLogEventsAssertions HaveMessage() } public void NotHaveMessage( - string messageTemplate = null, + string? messageTemplate = null, string because = "", params object[] becauseArgs) { + if (messageTemplate != null) + { + NotHaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); + } + else + { + NotHaveMessage(null, messageTemplate, because, becauseArgs); + } + } + + public void NotHaveMessage(Func? predicate, string? predicateErrorName = null, string because = "", params object[] becauseArgs) + { + predicateErrorName ??= ""; + int count; string failureMessage; - if (messageTemplate != null) + if (predicate != null) { count = Subject - .LogEvents - .Count(logEvent => logEvent.MessageTemplate.Text == messageTemplate); - - failureMessage = $"Expected message \"{messageTemplate}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; + .LogEvents + .Count(predicate); + + failureMessage = $"Expected message \"{predicateErrorName}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; } else { diff --git a/src/Serilog.Sinks.InMemory.FluentAssertions5/InMemorySinkAssertions.cs b/src/Serilog.Sinks.InMemory.FluentAssertions5/InMemorySinkAssertions.cs index b4d0c68..9ceca73 100644 --- a/src/Serilog.Sinks.InMemory.FluentAssertions5/InMemorySinkAssertions.cs +++ b/src/Serilog.Sinks.InMemory.FluentAssertions5/InMemorySinkAssertions.cs @@ -1,43 +1,34 @@ -using System.Linq; +#nullable enable + +using System; +using System.Linq; using FluentAssertions.Execution; using FluentAssertions.Primitives; +using Serilog.Events; using Serilog.Sinks.InMemory.Assertions; namespace Serilog.Sinks.InMemory.FluentAssertions5 { public class InMemorySinkAssertionsImpl : ReferenceTypeAssertions, InMemorySinkAssertions { - public InMemorySinkAssertionsImpl(InMemorySink snapshotInstance) : base(snapshotInstance) - { - } - - /* - * Hack attack. - * - * This is a bit of a dirty way to work around snapshotting the InMemorySink instance - * to ensure that you won't get hit by an InvalidOperationException when calling - * HaveMessage() and the logger gets called from somewhere else and adds a new - * LogEvent to the collection while that method is invoked. - * - * For now we copy the LogEvents from the current sink and use reflection to assign - * it to a new instance of InMemorySink that will be used by the assertions, - * effectively creating a snapshot of the InMemorySink that was used by the tests. - */ - private static InMemorySink SnapshotOf(InMemorySink instance) + public InMemorySinkAssertionsImpl(InMemorySink snapshotInstance) + : base(snapshotInstance) { - return instance.Snapshot(); } protected override string Identifier => nameof(InMemorySink); public LogEventsAssertions HaveMessage( - string messageTemplate, + Func predicate, + string? predicateErrorName = null, string because = "", params object[] becauseArgs) { + predicateErrorName ??= ""; + var matches = Subject .LogEvents - .Where(logEvent => logEvent.MessageTemplate.Text == messageTemplate) + .Where(predicate) .ToList(); Execute.Assertion @@ -45,9 +36,17 @@ public LogEventsAssertions HaveMessage( .ForCondition(matches.Any()) .FailWith( "Expected message {0} to be logged", - messageTemplate); + predicateErrorName); - return new LogEventsAssertionsImpl(messageTemplate, matches); + return new LogEventsAssertionsImpl(predicateErrorName, matches); + } + + public LogEventsAssertions HaveMessage( + string messageTemplate, + string because = "", + params object[] becauseArgs) + { + return HaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); } public PatternLogEventsAssertions HaveMessage() @@ -56,20 +55,34 @@ public PatternLogEventsAssertions HaveMessage() } public void NotHaveMessage( - string messageTemplate = null, + string? messageTemplate = null, string because = "", params object[] becauseArgs) { + if (messageTemplate != null) + { + NotHaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); + } + else + { + NotHaveMessage(null, messageTemplate, because, becauseArgs); + } + } + + public void NotHaveMessage(Func? predicate, string? predicateErrorName = null, string because = "", params object[] becauseArgs) + { + predicateErrorName ??= ""; + int count; string failureMessage; - if (messageTemplate != null) + if (predicate != null) { count = Subject - .LogEvents - .Count(logEvent => logEvent.MessageTemplate.Text == messageTemplate); - - failureMessage = $"Expected message \"{messageTemplate}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; + .LogEvents + .Count(predicate); + + failureMessage = $"Expected message \"{predicateErrorName}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; } else { diff --git a/src/Serilog.Sinks.InMemory.FluentAssertions5/InMemorySinkAssertionsFactoryImpl.cs b/src/Serilog.Sinks.InMemory.FluentAssertions5/InMemorySinkAssertionsFactoryImpl.cs new file mode 100644 index 0000000..f3099ab --- /dev/null +++ b/src/Serilog.Sinks.InMemory.FluentAssertions5/InMemorySinkAssertionsFactoryImpl.cs @@ -0,0 +1,12 @@ +using Serilog.Sinks.InMemory.Assertions; + +namespace Serilog.Sinks.InMemory.FluentAssertions5 +{ + public sealed class InMemorySinkAssertionsFactoryImpl : InMemorySinkAssertionsFactory + { + public InMemorySinkAssertions CreateInMemorySinkAssertions(InMemorySink snapshotInstance) + { + return new InMemorySinkAssertionsImpl(snapshotInstance); + } + } +} \ No newline at end of file diff --git a/src/Serilog.Sinks.InMemory.FluentAssertions6/InMemorySinkAssertions.cs b/src/Serilog.Sinks.InMemory.FluentAssertions6/InMemorySinkAssertions.cs index ba93fd9..526c455 100644 --- a/src/Serilog.Sinks.InMemory.FluentAssertions6/InMemorySinkAssertions.cs +++ b/src/Serilog.Sinks.InMemory.FluentAssertions6/InMemorySinkAssertions.cs @@ -1,43 +1,34 @@ -using System.Linq; +#nullable enable + +using System; +using System.Linq; using FluentAssertions.Execution; using FluentAssertions.Primitives; +using Serilog.Events; using Serilog.Sinks.InMemory.Assertions; namespace Serilog.Sinks.InMemory.FluentAssertions6 { public class InMemorySinkAssertionsImpl : ReferenceTypeAssertions, InMemorySinkAssertions { - public InMemorySinkAssertionsImpl(InMemorySink snapshotInstance) : base(snapshotInstance) - { - } - - /* - * Hack attack. - * - * This is a bit of a dirty way to work around snapshotting the InMemorySink instance - * to ensure that you won't get hit by an InvalidOperationException when calling - * HaveMessage() and the logger gets called from somewhere else and adds a new - * LogEvent to the collection while that method is invoked. - * - * For now we copy the LogEvents from the current sink and use reflection to assign - * it to a new instance of InMemorySink that will be used by the assertions, - * effectively creating a snapshot of the InMemorySink that was used by the tests. - */ - private static InMemorySink SnapshotOf(InMemorySink instance) + public InMemorySinkAssertionsImpl(InMemorySink snapshotInstance) + : base(snapshotInstance) { - return instance.Snapshot(); } protected override string Identifier => nameof(InMemorySink); public LogEventsAssertions HaveMessage( - string messageTemplate, + Func predicate, + string? predicateErrorName = null, string because = "", params object[] becauseArgs) { + predicateErrorName ??= ""; + var matches = Subject .LogEvents - .Where(logEvent => logEvent.MessageTemplate.Text == messageTemplate) + .Where(predicate) .ToList(); Execute.Assertion @@ -45,9 +36,17 @@ public LogEventsAssertions HaveMessage( .ForCondition(matches.Any()) .FailWith( "Expected message {0} to be logged", - messageTemplate); + predicateErrorName); - return new LogEventsAssertionsImpl(messageTemplate, matches); + return new LogEventsAssertionsImpl(predicateErrorName, matches); + } + + public LogEventsAssertions HaveMessage( + string messageTemplate, + string because = "", + params object[] becauseArgs) + { + return HaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); } public PatternLogEventsAssertions HaveMessage() @@ -56,20 +55,34 @@ public PatternLogEventsAssertions HaveMessage() } public void NotHaveMessage( - string messageTemplate = null, + string? messageTemplate = null, string because = "", params object[] becauseArgs) { + if (messageTemplate != null) + { + NotHaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); + } + else + { + NotHaveMessage(null, messageTemplate, because, becauseArgs); + } + } + + public void NotHaveMessage(Func? predicate, string? predicateErrorName = null, string because = "", params object[] becauseArgs) + { + predicateErrorName ??= ""; + int count; string failureMessage; - if (messageTemplate != null) + if (predicate != null) { count = Subject - .LogEvents - .Count(logEvent => logEvent.MessageTemplate.Text == messageTemplate); - - failureMessage = $"Expected message \"{messageTemplate}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; + .LogEvents + .Count(predicate); + + failureMessage = $"Expected message \"{predicateErrorName}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; } else { diff --git a/src/Serilog.Sinks.InMemory.FluentAssertions6/InMemorySinkAssertionsFactoryImpl.cs b/src/Serilog.Sinks.InMemory.FluentAssertions6/InMemorySinkAssertionsFactoryImpl.cs new file mode 100644 index 0000000..d781c3f --- /dev/null +++ b/src/Serilog.Sinks.InMemory.FluentAssertions6/InMemorySinkAssertionsFactoryImpl.cs @@ -0,0 +1,12 @@ +using Serilog.Sinks.InMemory.Assertions; + +namespace Serilog.Sinks.InMemory.FluentAssertions6 +{ + public sealed class InMemorySinkAssertionsFactoryImpl : InMemorySinkAssertionsFactory + { + public InMemorySinkAssertions CreateInMemorySinkAssertions(InMemorySink snapshotInstance) + { + return new InMemorySinkAssertionsImpl(snapshotInstance); + } + } +} \ No newline at end of file diff --git a/src/Serilog.Sinks.InMemory.FluentAssertions7/InMemorySinkAssertions.cs b/src/Serilog.Sinks.InMemory.FluentAssertions7/InMemorySinkAssertions.cs index 6d735f4..3595be6 100644 --- a/src/Serilog.Sinks.InMemory.FluentAssertions7/InMemorySinkAssertions.cs +++ b/src/Serilog.Sinks.InMemory.FluentAssertions7/InMemorySinkAssertions.cs @@ -1,43 +1,34 @@ -using System.Linq; +#nullable enable + +using System; +using System.Linq; using FluentAssertions.Execution; using FluentAssertions.Primitives; +using Serilog.Events; using Serilog.Sinks.InMemory.Assertions; namespace Serilog.Sinks.InMemory.FluentAssertions7 { public class InMemorySinkAssertionsImpl : ReferenceTypeAssertions, InMemorySinkAssertions { - public InMemorySinkAssertionsImpl(InMemorySink snapshotInstance) : base(snapshotInstance) - { - } - - /* - * Hack attack. - * - * This is a bit of a dirty way to work around snapshotting the InMemorySink instance - * to ensure that you won't get hit by an InvalidOperationException when calling - * HaveMessage() and the logger gets called from somewhere else and adds a new - * LogEvent to the collection while that method is invoked. - * - * For now we copy the LogEvents from the current sink and use reflection to assign - * it to a new instance of InMemorySink that will be used by the assertions, - * effectively creating a snapshot of the InMemorySink that was used by the tests. - */ - private static InMemorySink SnapshotOf(InMemorySink instance) + public InMemorySinkAssertionsImpl(InMemorySink snapshotInstance) + : base(snapshotInstance) { - return instance.Snapshot(); } protected override string Identifier => nameof(InMemorySink); public LogEventsAssertions HaveMessage( - string messageTemplate, + Func predicate, + string? predicateErrorName = null, string because = "", params object[] becauseArgs) { + predicateErrorName ??= ""; + var matches = Subject .LogEvents - .Where(logEvent => logEvent.MessageTemplate.Text == messageTemplate) + .Where(predicate) .ToList(); Execute.Assertion @@ -45,9 +36,17 @@ public LogEventsAssertions HaveMessage( .ForCondition(matches.Any()) .FailWith( "Expected message {0} to be logged", - messageTemplate); + predicateErrorName); - return new LogEventsAssertionsImpl(messageTemplate, matches); + return new LogEventsAssertionsImpl(predicateErrorName, matches); + } + + public LogEventsAssertions HaveMessage( + string messageTemplate, + string because = "", + params object[] becauseArgs) + { + return HaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); } public PatternLogEventsAssertions HaveMessage() @@ -56,20 +55,34 @@ public PatternLogEventsAssertions HaveMessage() } public void NotHaveMessage( - string messageTemplate = null, + string? messageTemplate = null, string because = "", params object[] becauseArgs) { + if (messageTemplate != null) + { + NotHaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); + } + else + { + NotHaveMessage(null, messageTemplate, because, becauseArgs); + } + } + + public void NotHaveMessage(Func? predicate, string? predicateErrorName = null, string because = "", params object[] becauseArgs) + { + predicateErrorName ??= ""; + int count; string failureMessage; - if (messageTemplate != null) + if (predicate != null) { count = Subject - .LogEvents - .Count(logEvent => logEvent.MessageTemplate.Text == messageTemplate); - - failureMessage = $"Expected message \"{messageTemplate}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; + .LogEvents + .Count(predicate); + + failureMessage = $"Expected message \"{predicateErrorName}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; } else { diff --git a/src/Serilog.Sinks.InMemory.FluentAssertions7/InMemorySinkAssertionsFactoryImpl.cs b/src/Serilog.Sinks.InMemory.FluentAssertions7/InMemorySinkAssertionsFactoryImpl.cs new file mode 100644 index 0000000..20f94c5 --- /dev/null +++ b/src/Serilog.Sinks.InMemory.FluentAssertions7/InMemorySinkAssertionsFactoryImpl.cs @@ -0,0 +1,12 @@ +using Serilog.Sinks.InMemory.Assertions; + +namespace Serilog.Sinks.InMemory.FluentAssertions7 +{ + public sealed class InMemorySinkAssertionsFactoryImpl : InMemorySinkAssertionsFactory + { + public InMemorySinkAssertions CreateInMemorySinkAssertions(InMemorySink snapshotInstance) + { + return new InMemorySinkAssertionsImpl(snapshotInstance); + } + } +} \ No newline at end of file diff --git a/src/Serilog.Sinks.InMemory.FluentAssertions8/InMemorySinkAssertionsFactoryImpl.cs b/src/Serilog.Sinks.InMemory.FluentAssertions8/InMemorySinkAssertionsFactoryImpl.cs new file mode 100644 index 0000000..1b54dc6 --- /dev/null +++ b/src/Serilog.Sinks.InMemory.FluentAssertions8/InMemorySinkAssertionsFactoryImpl.cs @@ -0,0 +1,12 @@ +using Serilog.Sinks.InMemory.Assertions; + +namespace Serilog.Sinks.InMemory.FluentAssertions8 +{ + public sealed class InMemorySinkAssertionsFactoryImpl : InMemorySinkAssertionsFactory + { + public InMemorySinkAssertions CreateInMemorySinkAssertions(InMemorySink snapshotInstance) + { + return new InMemorySinkAssertionsImpl(snapshotInstance); + } + } +} \ No newline at end of file diff --git a/src/Serilog.Sinks.InMemory.FluentAssertions8/InMemorySinkAssertionsImpl.cs b/src/Serilog.Sinks.InMemory.FluentAssertions8/InMemorySinkAssertionsImpl.cs index 0c86c9d..e97e071 100644 --- a/src/Serilog.Sinks.InMemory.FluentAssertions8/InMemorySinkAssertionsImpl.cs +++ b/src/Serilog.Sinks.InMemory.FluentAssertions8/InMemorySinkAssertionsImpl.cs @@ -1,53 +1,52 @@ -using System.Linq; +#nullable enable + +using System; +using System.Linq; using FluentAssertions.Execution; using FluentAssertions.Primitives; +using Serilog.Events; using Serilog.Sinks.InMemory.Assertions; namespace Serilog.Sinks.InMemory.FluentAssertions8 { public class InMemorySinkAssertionsImpl : ReferenceTypeAssertions, InMemorySinkAssertions { - public InMemorySinkAssertionsImpl(InMemorySink snapshotInstance) : base(snapshotInstance, AssertionChain.GetOrCreate()) - { - } - - /* - * Hack attack. - * - * This is a bit of a dirty way to work around snapshotting the InMemorySink instance - * to ensure that you won't get hit by an InvalidOperationException when calling - * HaveMessage() and the logger gets called from somewhere else and adds a new - * LogEvent to the collection while that method is invoked. - * - * For now we copy the LogEvents from the current sink and use reflection to assign - * it to a new instance of InMemorySink that will be used by the assertions, - * effectively creating a snapshot of the InMemorySink that was used by the tests. - */ - private static InMemorySink SnapshotOf(InMemorySink instance) + public InMemorySinkAssertionsImpl(InMemorySink snapshotInstance) + : base(snapshotInstance, AssertionChain.GetOrCreate()) { - return instance.Snapshot(); } protected override string Identifier => nameof(InMemorySink); public LogEventsAssertions HaveMessage( - string messageTemplate, + Func predicate, + string? predicateErrorName = null, string because = "", params object[] becauseArgs) { + predicateErrorName ??= ""; + var matches = Subject .LogEvents - .Where(logEvent => logEvent.MessageTemplate.Text == messageTemplate) - .ToList(); + .Where(predicate) + .ToArray(); CurrentAssertionChain .BecauseOf(because, becauseArgs) .ForCondition(matches.Any()) .FailWith( "Expected message {0} to be logged", - messageTemplate); + predicateErrorName); - return new LogEventsAssertionsImpl(messageTemplate, matches, CurrentAssertionChain); + return new LogEventsAssertionsImpl(predicateErrorName, matches, CurrentAssertionChain); + } + + public LogEventsAssertions HaveMessage( + string messageTemplate, + string because = "", + params object[] becauseArgs) + { + return HaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); } public PatternLogEventsAssertions HaveMessage() @@ -56,20 +55,34 @@ public PatternLogEventsAssertions HaveMessage() } public void NotHaveMessage( - string messageTemplate = null, + string? messageTemplate = null, string because = "", params object[] becauseArgs) { + if (messageTemplate != null) + { + NotHaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); + } + else + { + NotHaveMessage(null, messageTemplate, because, becauseArgs); + } + } + + public void NotHaveMessage(Func? predicate, string? predicateErrorName = null, string because = "", params object[] becauseArgs) + { + predicateErrorName ??= ""; + int count; string failureMessage; - if (messageTemplate != null) + if (predicate != null) { count = Subject - .LogEvents - .Count(logEvent => logEvent.MessageTemplate.Text == messageTemplate); - - failureMessage = $"Expected message \"{messageTemplate}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; + .LogEvents + .Count(predicate); + + failureMessage = $"Expected message \"{predicateErrorName}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; } else { diff --git a/src/Serilog.Sinks.InMemory.Shouldly4/InMemorySinkAssertionsFactoryImpl.cs b/src/Serilog.Sinks.InMemory.Shouldly4/InMemorySinkAssertionsFactoryImpl.cs new file mode 100644 index 0000000..f765c68 --- /dev/null +++ b/src/Serilog.Sinks.InMemory.Shouldly4/InMemorySinkAssertionsFactoryImpl.cs @@ -0,0 +1,12 @@ +using Serilog.Sinks.InMemory.Assertions; + +namespace Serilog.Sinks.InMemory.Shouldly4 +{ + public sealed class InMemorySinkAssertionsFactoryImpl : InMemorySinkAssertionsFactory + { + public InMemorySinkAssertions CreateInMemorySinkAssertions(InMemorySink snapshotInstance) + { + return new InMemorySinkAssertionsImpl(snapshotInstance); + } + } +} \ No newline at end of file diff --git a/src/Serilog.Sinks.InMemory.Shouldly4/InMemorySinkAssertionsImpl.cs b/src/Serilog.Sinks.InMemory.Shouldly4/InMemorySinkAssertionsImpl.cs index 5fdbc90..dc78024 100644 --- a/src/Serilog.Sinks.InMemory.Shouldly4/InMemorySinkAssertionsImpl.cs +++ b/src/Serilog.Sinks.InMemory.Shouldly4/InMemorySinkAssertionsImpl.cs @@ -1,78 +1,90 @@ -using System.Linq; +#nullable enable + +using System; +using System.Linq; +using Serilog.Events; using Serilog.Sinks.InMemory.Assertions; using Shouldly; namespace Serilog.Sinks.InMemory.Shouldly4 { - [ShouldlyMethods] + [ShouldlyMethods] public class InMemorySinkAssertionsImpl : InMemorySinkAssertions { - private readonly InMemorySink _snapshotInstance; - public InMemorySinkAssertionsImpl(InMemorySink snapshotInstance) { - _snapshotInstance = snapshotInstance; - } - - /* - * Hack attack. - * - * This is a bit of a dirty way to work around snapshotting the InMemorySink instance - * to ensure that you won't get hit by an InvalidOperationException when calling - * HaveMessage() and the logger gets called from somewhere else and adds a new - * LogEvent to the collection while that method is invoked. - * - * For now we copy the LogEvents from the current sink and use reflection to assign - * it to a new instance of InMemorySink that will be used by the assertions, - * effectively creating a snapshot of the InMemorySink that was used by the tests. - */ - private static InMemorySink SnapshotOf(InMemorySink instance) - { - return instance.Snapshot(); + Subject = snapshotInstance; } + public InMemorySink Subject { get; } + public LogEventsAssertions HaveMessage( - string messageTemplate, + Func predicate, + string? predicateErrorName = null, string because = "", params object[] becauseArgs) { - var matches = _snapshotInstance + predicateErrorName ??= ""; + + var matches = Subject .LogEvents - .Where(logEvent => logEvent.MessageTemplate.Text == messageTemplate) - .ToList(); + .Where(predicate) + .ToArray(); if (!matches.Any()) { - throw new ShouldAssertException($"Expected message \"{messageTemplate}\" to be logged"); + throw new ShouldAssertException($"Expected message \"{predicateErrorName}\" to be logged"); } - return new LogEventsAssertionsImpl(messageTemplate, matches); + return new LogEventsAssertionsImpl(predicateErrorName, matches); + } + + public LogEventsAssertions HaveMessage( + string messageTemplate, + string because = "", + params object[] becauseArgs) + { + return HaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); } public PatternLogEventsAssertions HaveMessage() { - return new PatternLogEventsAssertionsImpl(_snapshotInstance.LogEvents); + return new PatternLogEventsAssertionsImpl(Subject.LogEvents); } public void NotHaveMessage( - string messageTemplate = null, + string? messageTemplate = null, string because = "", params object[] becauseArgs) { + if (messageTemplate != null) + { + NotHaveMessage(logEvent => logEvent.MessageTemplate.Text == messageTemplate, messageTemplate, because, becauseArgs); + } + else + { + NotHaveMessage(null, messageTemplate, because, becauseArgs); + } + } + + public void NotHaveMessage(Func? predicate, string? predicateErrorName = null, string because = "", params object[] becauseArgs) + { + predicateErrorName ??= ""; + int count; string failureMessage; - if (messageTemplate != null) + if (predicate != null) { - count = _snapshotInstance - .LogEvents - .Count(logEvent => logEvent.MessageTemplate.Text == messageTemplate); - - failureMessage = $"Expected message \"{messageTemplate}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; + count = Subject + .LogEvents + .Count(predicate); + + failureMessage = $"Expected message \"{predicateErrorName}\" not to be logged, but it was found {(count > 1 ? $"{count} times" : "once")}"; } else { - count = _snapshotInstance + count = Subject .LogEvents .Count(); From 1bebb6383be35ba811b0a98d19f776687cf3c485 Mon Sep 17 00:00:00 2001 From: VasiliyF Date: Thu, 23 Oct 2025 07:02:03 +0200 Subject: [PATCH 4/4] Simplify adding new Assertions tests --- ...tions.Tests.Unit.AwesomeAssertions8.csproj | 27 +++---------------- ...tions.Tests.Unit.AwesomeAssertions9.csproj | 25 ++--------------- ...rtions.Tests.Unit.FluentAssertions5.csproj | 25 ++--------------- ...rtions.Tests.Unit.FluentAssertions6.csproj | 27 +++---------------- ...rtions.Tests.Unit.FluentAssertions7.csproj | 25 ++--------------- ...rtions.Tests.Unit.FluentAssertions8.csproj | 25 ++--------------- 6 files changed, 14 insertions(+), 140 deletions(-) diff --git a/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions8/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions8.csproj b/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions8/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions8.csproj index 279aa4a..1a514ab 100644 --- a/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions8/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions8.csproj +++ b/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions8/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions8.csproj @@ -26,29 +26,8 @@ - - Repro.cs - - - WhenAssertingAndSInkIsWrittenTo.cs - - - WhenAssertingLogEventHasLevel.cs - - - WhenAssertingLogEventsExist.cs - - - WhenAssertingMessageExistsThatContainsPattern.cs - - - WhenAssertingPropertyValuesOnMultipleMessages.cs - - - WhenAssertingScalarLogPropertyExists.cs - - - WhenAssertingStructuredLogPropertyExists.cs - + + %(RecursiveDir)%(Filename)%(Extension) + diff --git a/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions9/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions9.csproj b/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions9/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions9.csproj index 374e9b3..8bad793 100644 --- a/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions9/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions9.csproj +++ b/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions9/Serilog.Sinks.InMemory.Assertions.Tests.Unit.AwesomeAssertions9.csproj @@ -26,29 +26,8 @@ - - Repro.cs - - - WhenAssertingAndSInkIsWrittenTo.cs - - - WhenAssertingLogEventHasLevel.cs - - - WhenAssertingLogEventsExist.cs - - - WhenAssertingMessageExistsThatContainsPattern.cs - - - WhenAssertingPropertyValuesOnMultipleMessages.cs - - - WhenAssertingScalarLogPropertyExists.cs - - - WhenAssertingStructuredLogPropertyExists.cs + + %(RecursiveDir)%(Filename)%(Extension) diff --git a/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions5/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions5.csproj b/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions5/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions5.csproj index fcf8f5a..316e155 100644 --- a/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions5/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions5.csproj +++ b/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions5/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions5.csproj @@ -26,29 +26,8 @@ - - Repro.cs - - - WhenAssertingAndSInkIsWrittenTo.cs - - - WhenAssertingLogEventHasLevel.cs - - - WhenAssertingLogEventsExist.cs - - - WhenAssertingMessageExistsThatContainsPattern.cs - - - WhenAssertingPropertyValuesOnMultipleMessages.cs - - - WhenAssertingScalarLogPropertyExists.cs - - - WhenAssertingStructuredLogPropertyExists.cs + + %(RecursiveDir)%(Filename)%(Extension) diff --git a/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions6/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions6.csproj b/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions6/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions6.csproj index 2eac58b..e97d6f0 100644 --- a/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions6/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions6.csproj +++ b/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions6/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions6.csproj @@ -24,31 +24,10 @@ - + - - Repro.cs - - - WhenAssertingAndSInkIsWrittenTo.cs - - - WhenAssertingLogEventHasLevel.cs - - - WhenAssertingLogEventsExist.cs - - - WhenAssertingMessageExistsThatContainsPattern.cs - - - WhenAssertingPropertyValuesOnMultipleMessages.cs - - - WhenAssertingScalarLogPropertyExists.cs - - - WhenAssertingStructuredLogPropertyExists.cs + + %(RecursiveDir)%(Filename)%(Extension) diff --git a/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions7/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions7.csproj b/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions7/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions7.csproj index 1aa8ddc..1a2fc57 100644 --- a/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions7/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions7.csproj +++ b/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions7/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions7.csproj @@ -26,29 +26,8 @@ - - Repro.cs - - - WhenAssertingAndSInkIsWrittenTo.cs - - - WhenAssertingLogEventHasLevel.cs - - - WhenAssertingLogEventsExist.cs - - - WhenAssertingMessageExistsThatContainsPattern.cs - - - WhenAssertingPropertyValuesOnMultipleMessages.cs - - - WhenAssertingScalarLogPropertyExists.cs - - - WhenAssertingStructuredLogPropertyExists.cs + + %(RecursiveDir)%(Filename)%(Extension) diff --git a/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions8/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions8.csproj b/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions8/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions8.csproj index 9d3bcf3..00512af 100644 --- a/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions8/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions8.csproj +++ b/test/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions8/Serilog.Sinks.InMemory.Assertions.Tests.Unit.FluentAssertions8.csproj @@ -26,29 +26,8 @@ - - Repro.cs - - - WhenAssertingAndSInkIsWrittenTo.cs - - - WhenAssertingLogEventHasLevel.cs - - - WhenAssertingLogEventsExist.cs - - - WhenAssertingMessageExistsThatContainsPattern.cs - - - WhenAssertingPropertyValuesOnMultipleMessages.cs - - - WhenAssertingScalarLogPropertyExists.cs - - - WhenAssertingStructuredLogPropertyExists.cs + + %(RecursiveDir)%(Filename)%(Extension)