From 76878f376ee06e87fd6118485a9a2dd15942a580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Sat, 28 Feb 2026 15:04:11 +0100 Subject: [PATCH 1/2] feat: update `VerificationResult` when interactions get added --- .../Interactions/MockInteractions.cs | 6 ++ Source/Mockolate/MockRegistration.Verify.cs | 66 +++++++------------ Source/Mockolate/Verify/VerificationResult.cs | 16 +++-- .../Expected/Mockolate_net10.0.txt | 3 +- .../Expected/Mockolate_net8.0.txt | 3 +- .../Expected/Mockolate_netstandard2.0.txt | 3 +- .../Interactions/MockInteractionsTests.cs | 38 +++++++++++ Tests/Mockolate.Tests/ItTests.MatchesTests.cs | 5 +- .../Mockolate.Tests/Verify/MockVerifyTests.cs | 20 ++++++ .../VerificationResultExtensionsTests.cs | 2 +- 10 files changed, 106 insertions(+), 56 deletions(-) create mode 100644 Tests/Mockolate.Tests/Interactions/MockInteractionsTests.cs diff --git a/Source/Mockolate/Interactions/MockInteractions.cs b/Source/Mockolate/Interactions/MockInteractions.cs index 37079931..21efa6c3 100644 --- a/Source/Mockolate/Interactions/MockInteractions.cs +++ b/Source/Mockolate/Interactions/MockInteractions.cs @@ -34,6 +34,7 @@ TInteraction IMockInteractions.RegisterInteraction(TInteraction in { _missingVerification?.Add(interaction); _interactions.TryAdd(interaction.Index, interaction); + InteractionAdded?.Invoke(this, interaction); return interaction; } @@ -46,6 +47,11 @@ public IReadOnlyCollection GetUnverifiedInteractions() return _missingVerification; } + /// + /// Notifies whenever an interaction was registered on the mock. + /// + public event EventHandler? InteractionAdded; + internal event EventHandler? OnClearing; internal int GetNextIndex() diff --git a/Source/Mockolate/MockRegistration.Verify.cs b/Source/Mockolate/MockRegistration.Verify.cs index f5e9dce7..8c080f40 100644 --- a/Source/Mockolate/MockRegistration.Verify.cs +++ b/Source/Mockolate/MockRegistration.Verify.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Mockolate.Interactions; @@ -18,11 +17,8 @@ public VerificationResult Method(T subject, IMethodMatch methodMatch) => new( subject, Interactions, - Interactions.Interactions - .OfType() - .Where(methodMatch.Matches) - .Cast() - .ToArray(), + interaction => interaction is MethodInvocation method && + methodMatch.Matches(method), $"invoked method {methodMatch}"); /// @@ -30,11 +26,8 @@ public VerificationResult Method(T subject, IMethodMatch methodMatch) /// public VerificationResult Property(T subject, string propertyName) => new(subject, Interactions, - Interactions.Interactions - .OfType() - .Where(property => property.Name.Equals(propertyName)) - .Cast() - .ToArray(), + interaction => interaction is PropertyGetterAccess property && + property.Name.Equals(propertyName), $"got property {propertyName.SubstringAfterLast('.')}"); /// @@ -45,12 +38,9 @@ public VerificationResult Property(T subject, string propertyName, IParameter value) => new(subject, Interactions, - Interactions.Interactions - .OfType() - .Where(property => property.Name.Equals(propertyName) && - value.Matches(property.Value)) - .Cast() - .ToArray(), + interaction => interaction is PropertySetterAccess property && + property.Name.Equals(propertyName) && + value.Matches(property.Value), $"set property {propertyName.SubstringAfterLast('.')} to value {value}"); /// @@ -61,14 +51,11 @@ public VerificationResult Indexer(T subject, params NamedParameter[] parameters) => new(subject, Interactions, - Interactions.Interactions - .OfType() - .Where(indexer => indexer.Parameters.Length == parameters.Length && - !parameters - .Where((parameter, i) => !parameter.Matches(indexer.Parameters[i])) - .Any()) - .Cast() - .ToArray(), + interaction => interaction is IndexerGetterAccess indexer && + indexer.Parameters.Length == parameters.Length && + !parameters + .Where((parameter, i) => !parameter.Matches(indexer.Parameters[i])) + .Any(), $"got indexer [{string.Join(", ", parameters.Select(x => x.Parameter.ToString()))}]"); /// @@ -79,15 +66,12 @@ public VerificationResult Indexer(T subject, IParameter? value, params NamedParameter[] parameters) => new(subject, Interactions, - Interactions.Interactions - .OfType() - .Where(indexer => indexer.Parameters.Length == parameters.Length && - (value?.Matches(indexer.Value) ?? indexer.Value is null) && - !parameters - .Where((parameter, i) => !parameter.Matches(indexer.Parameters[i])) - .Any()) - .Cast() - .ToArray(), + interaction => interaction is IndexerSetterAccess indexer && + indexer.Parameters.Length == parameters.Length && + (value?.Matches(indexer.Value) ?? indexer.Value is null) && + !parameters + .Where((parameter, i) => !parameter.Matches(indexer.Parameters[i])) + .Any(), $"set indexer [{string.Join(", ", parameters.Select(x => x.Parameter.ToString()))}] to value {value?.ToString() ?? "null"}"); /// @@ -95,11 +79,8 @@ public VerificationResult Indexer(T subject, IParameter? value, /// public VerificationResult SubscribedTo(T subject, string eventName) => new(subject, Interactions, - Interactions.Interactions - .OfType() - .Where(@event => @event.Name.Equals(eventName)) - .Cast() - .ToArray(), + interaction => interaction is EventSubscription @event && + @event.Name.Equals(eventName), $"subscribed to event {eventName.SubstringAfterLast('.')}"); /// @@ -107,11 +88,8 @@ public VerificationResult SubscribedTo(T subject, string eventName) /// public VerificationResult UnsubscribedFrom(T subject, string eventName) => new(subject, Interactions, - Interactions.Interactions - .OfType() - .Where(@event => @event.Name.Equals(eventName)) - .Cast() - .ToArray(), + interaction => interaction is EventUnsubscription @event && + @event.Name.Equals(eventName), $"unsubscribed from event {eventName.SubstringAfterLast('.')}"); /// diff --git a/Source/Mockolate/Verify/VerificationResult.cs b/Source/Mockolate/Verify/VerificationResult.cs index e2967aaa..676d2ec8 100644 --- a/Source/Mockolate/Verify/VerificationResult.cs +++ b/Source/Mockolate/Verify/VerificationResult.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Mockolate.Interactions; namespace Mockolate.Verify; @@ -10,16 +11,18 @@ public class VerificationResult : IVerificationResult, IVerifi { private readonly string _expectation; private readonly MockInteractions _interactions; - private readonly IInteraction[] _matchingInteractions; + private readonly Func _predicate; private readonly TVerify _verify; /// - public VerificationResult(TVerify verify, MockInteractions interactions, IInteraction[] matchingInteractions, + public VerificationResult(TVerify verify, + MockInteractions interactions, + Func predicate, string expectation) { _verify = verify; _interactions = interactions; - _matchingInteractions = matchingInteractions; + _predicate = predicate; _expectation = expectation; } @@ -32,7 +35,7 @@ TVerify IVerificationResult.Object #endregion internal VerificationResult Map(T mock) - => new(mock, _interactions, _matchingInteractions, _expectation); + => new(mock, _interactions, _predicate, _expectation); #region IVerificationResult @@ -47,8 +50,9 @@ MockInteractions IVerificationResult.MockInteractions /// bool IVerificationResult.Verify(Func predicate) { - _interactions.Verified(_matchingInteractions); - return predicate(_matchingInteractions); + IInteraction[] matchingInteractions = _interactions.Interactions.Where(_predicate).ToArray(); + _interactions.Verified(matchingInteractions); + return predicate(matchingInteractions); } #endregion diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt index 7edfbf5d..7e2f6bde 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt @@ -534,6 +534,7 @@ namespace Mockolate.Interactions public MockInteractions() { } public int Count { get; } public System.Collections.Generic.IEnumerable Interactions { get; } + public event System.EventHandler? InteractionAdded; public System.Collections.Generic.IReadOnlyCollection GetUnverifiedInteractions() { } } [System.Diagnostics.DebuggerDisplay("{ToString()}")] @@ -1796,7 +1797,7 @@ namespace Mockolate.Verify } public class VerificationResult : Mockolate.Verify.IVerificationResult, Mockolate.Verify.IVerificationResult { - public VerificationResult(TVerify verify, Mockolate.Interactions.MockInteractions interactions, Mockolate.Interactions.IInteraction[] matchingInteractions, string expectation) { } + public VerificationResult(TVerify verify, Mockolate.Interactions.MockInteractions interactions, System.Func predicate, string expectation) { } } } namespace Mockolate.Web diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt index 8199b859..a87667e4 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt @@ -533,6 +533,7 @@ namespace Mockolate.Interactions public MockInteractions() { } public int Count { get; } public System.Collections.Generic.IEnumerable Interactions { get; } + public event System.EventHandler? InteractionAdded; public System.Collections.Generic.IReadOnlyCollection GetUnverifiedInteractions() { } } [System.Diagnostics.DebuggerDisplay("{ToString()}")] @@ -1795,7 +1796,7 @@ namespace Mockolate.Verify } public class VerificationResult : Mockolate.Verify.IVerificationResult, Mockolate.Verify.IVerificationResult { - public VerificationResult(TVerify verify, Mockolate.Interactions.MockInteractions interactions, Mockolate.Interactions.IInteraction[] matchingInteractions, string expectation) { } + public VerificationResult(TVerify verify, Mockolate.Interactions.MockInteractions interactions, System.Func predicate, string expectation) { } } } namespace Mockolate.Web diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt index 715d3b1c..4fc1f9a8 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt @@ -500,6 +500,7 @@ namespace Mockolate.Interactions public MockInteractions() { } public int Count { get; } public System.Collections.Generic.IEnumerable Interactions { get; } + public event System.EventHandler? InteractionAdded; public System.Collections.Generic.IReadOnlyCollection GetUnverifiedInteractions() { } } [System.Diagnostics.DebuggerDisplay("{ToString()}")] @@ -1744,7 +1745,7 @@ namespace Mockolate.Verify } public class VerificationResult : Mockolate.Verify.IVerificationResult, Mockolate.Verify.IVerificationResult { - public VerificationResult(TVerify verify, Mockolate.Interactions.MockInteractions interactions, Mockolate.Interactions.IInteraction[] matchingInteractions, string expectation) { } + public VerificationResult(TVerify verify, Mockolate.Interactions.MockInteractions interactions, System.Func predicate, string expectation) { } } } namespace Mockolate.Web diff --git a/Tests/Mockolate.Tests/Interactions/MockInteractionsTests.cs b/Tests/Mockolate.Tests/Interactions/MockInteractionsTests.cs new file mode 100644 index 00000000..5674bab0 --- /dev/null +++ b/Tests/Mockolate.Tests/Interactions/MockInteractionsTests.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Mockolate.Interactions; + +namespace Mockolate.Tests.Interactions; + +public class MockInteractionsTests +{ + [Fact] + public async Task InteractionAdded_ShouldIncludeInteraction() + { + List addedInteractions = []; + MockInteractions sut = new(); + MethodInvocation interaction = new(0, "foo", []); + sut.InteractionAdded += OnInteractionAdded; + + ((IMockInteractions)sut).RegisterInteraction(interaction); + + sut.InteractionAdded -= OnInteractionAdded; + + await That(addedInteractions).HasSingle().Which.IsSameAs(interaction); + + void OnInteractionAdded(object? sender, IInteraction e) + { + addedInteractions.Add(e); + } + } + + [Fact] + public async Task RegisterInteraction_ShouldRegisterInteraction() + { + MockInteractions sut = new(); + MethodInvocation interaction = new(0, "foo", []); + + MethodInvocation registeredInteraction = ((IMockInteractions)sut).RegisterInteraction(interaction); + + await That(registeredInteraction).IsSameAs(interaction); + } +} diff --git a/Tests/Mockolate.Tests/ItTests.MatchesTests.cs b/Tests/Mockolate.Tests/ItTests.MatchesTests.cs index 41e479da..82205d1e 100644 --- a/Tests/Mockolate.Tests/ItTests.MatchesTests.cs +++ b/Tests/Mockolate.Tests/ItTests.MatchesTests.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using Mockolate.Parameters; +using Mockolate.Verify; namespace Mockolate.Tests; @@ -58,8 +59,8 @@ public async Task AsRegex_WithTimeout_ShouldApplyTimeoutToRegex() void Act() { - _ = mock.VerifyMock.Invoked.DoSomethingWithString( - It.Matches("F[aeiou]+o").AsRegex(timeout: TimeSpan.FromSeconds(0))); + mock.VerifyMock.Invoked.DoSomethingWithString( + It.Matches("F[aeiou]+o").AsRegex(timeout: TimeSpan.FromSeconds(0))).AtLeastOnce(); } await That(Act) diff --git a/Tests/Mockolate.Tests/Verify/MockVerifyTests.cs b/Tests/Mockolate.Tests/Verify/MockVerifyTests.cs index 93a1b272..24f06641 100644 --- a/Tests/Mockolate.Tests/Verify/MockVerifyTests.cs +++ b/Tests/Mockolate.Tests/Verify/MockVerifyTests.cs @@ -259,4 +259,24 @@ public async Task ThatAllSetupsAreUsed_WithUsedSetup_ShouldReturnTrue() await That(sut.VerifyMock.ThatAllSetupsAreUsed()).IsTrue(); } + + [Fact] + public async Task VerificationResult_ShouldUpdateWhenInteractionsChange() + { + IChocolateDispenser sut = Mock.Create(); + + IVerificationResult result = sut.VerifyMock.Invoked.Dispense(It.Is("Dark"), It.IsAny()); + bool r0 = result.Verify(f => f.Length == 0); + sut.Dispense("Dark", 1); + bool r1 = result.Verify(f => f.Length == 1); + sut.Dispense("White", 2); + bool r2 = result.Verify(f => f.Length == 1); + sut.Dispense("Dark", 2); + bool r3 = result.Verify(f => f.Length == 2); + + await That(r0).IsTrue().Because("No interaction was performed yet"); + await That(r1).IsTrue().Because("One interaction was performed"); + await That(r2).IsTrue().Because("The second interactions did not match"); + await That(r3).IsTrue().Because("The third interactions did again match"); + } } diff --git a/Tests/Mockolate.Tests/Verify/VerificationResultExtensionsTests.cs b/Tests/Mockolate.Tests/Verify/VerificationResultExtensionsTests.cs index 4323fdde..21dfbcfd 100644 --- a/Tests/Mockolate.Tests/Verify/VerificationResultExtensionsTests.cs +++ b/Tests/Mockolate.Tests/Verify/VerificationResultExtensionsTests.cs @@ -347,7 +347,7 @@ await That(void () => mock.VerifyMock.Invoked.Dispense(It.IsAny(), It.Is public async Task Then_WhenNoMock_ShouldThrowMockException() { IChocolateDispenser mock = new MyChocolateDispenser(); - VerificationResult result = new(mock, new MockInteractions(), [], "foo"); + VerificationResult result = new(mock, new MockInteractions(), _ => false, "foo"); void Act() { From e3e55246d04928dc0f86bf53b91d4142c2af7238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Sat, 28 Feb 2026 17:11:13 +0100 Subject: [PATCH 2/2] Fix review issues --- Source/Mockolate/Interactions/MockInteractions.cs | 8 ++------ .../Expected/Mockolate_net10.0.txt | 1 - .../Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt | 1 - .../Expected/Mockolate_netstandard2.0.txt | 1 - .../MockInteractionsTests.cs | 11 +++++------ Tests/Mockolate.Tests/Verify/MockVerifyTests.cs | 4 ++-- 6 files changed, 9 insertions(+), 17 deletions(-) rename Tests/{Mockolate.Tests/Interactions => Mockolate.Internal.Tests}/MockInteractionsTests.cs (72%) diff --git a/Source/Mockolate/Interactions/MockInteractions.cs b/Source/Mockolate/Interactions/MockInteractions.cs index 21efa6c3..ff0475ff 100644 --- a/Source/Mockolate/Interactions/MockInteractions.cs +++ b/Source/Mockolate/Interactions/MockInteractions.cs @@ -34,7 +34,7 @@ TInteraction IMockInteractions.RegisterInteraction(TInteraction in { _missingVerification?.Add(interaction); _interactions.TryAdd(interaction.Index, interaction); - InteractionAdded?.Invoke(this, interaction); + InteractionAdded?.Invoke(this, EventArgs.Empty); return interaction; } @@ -47,11 +47,7 @@ public IReadOnlyCollection GetUnverifiedInteractions() return _missingVerification; } - /// - /// Notifies whenever an interaction was registered on the mock. - /// - public event EventHandler? InteractionAdded; - + internal event EventHandler? InteractionAdded; internal event EventHandler? OnClearing; internal int GetNextIndex() diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt index 7e2f6bde..5b8cc9fd 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt @@ -534,7 +534,6 @@ namespace Mockolate.Interactions public MockInteractions() { } public int Count { get; } public System.Collections.Generic.IEnumerable Interactions { get; } - public event System.EventHandler? InteractionAdded; public System.Collections.Generic.IReadOnlyCollection GetUnverifiedInteractions() { } } [System.Diagnostics.DebuggerDisplay("{ToString()}")] diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt index a87667e4..a8f4c048 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt @@ -533,7 +533,6 @@ namespace Mockolate.Interactions public MockInteractions() { } public int Count { get; } public System.Collections.Generic.IEnumerable Interactions { get; } - public event System.EventHandler? InteractionAdded; public System.Collections.Generic.IReadOnlyCollection GetUnverifiedInteractions() { } } [System.Diagnostics.DebuggerDisplay("{ToString()}")] diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt index 4fc1f9a8..6e79b660 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_netstandard2.0.txt @@ -500,7 +500,6 @@ namespace Mockolate.Interactions public MockInteractions() { } public int Count { get; } public System.Collections.Generic.IEnumerable Interactions { get; } - public event System.EventHandler? InteractionAdded; public System.Collections.Generic.IReadOnlyCollection GetUnverifiedInteractions() { } } [System.Diagnostics.DebuggerDisplay("{ToString()}")] diff --git a/Tests/Mockolate.Tests/Interactions/MockInteractionsTests.cs b/Tests/Mockolate.Internal.Tests/MockInteractionsTests.cs similarity index 72% rename from Tests/Mockolate.Tests/Interactions/MockInteractionsTests.cs rename to Tests/Mockolate.Internal.Tests/MockInteractionsTests.cs index 5674bab0..136c7479 100644 --- a/Tests/Mockolate.Tests/Interactions/MockInteractionsTests.cs +++ b/Tests/Mockolate.Internal.Tests/MockInteractionsTests.cs @@ -1,14 +1,13 @@ -using System.Collections.Generic; using Mockolate.Interactions; -namespace Mockolate.Tests.Interactions; +namespace Mockolate.Internal.Tests; public class MockInteractionsTests { [Fact] public async Task InteractionAdded_ShouldIncludeInteraction() { - List addedInteractions = []; + int interactionCount = 0; MockInteractions sut = new(); MethodInvocation interaction = new(0, "foo", []); sut.InteractionAdded += OnInteractionAdded; @@ -17,11 +16,11 @@ public async Task InteractionAdded_ShouldIncludeInteraction() sut.InteractionAdded -= OnInteractionAdded; - await That(addedInteractions).HasSingle().Which.IsSameAs(interaction); + await That(interactionCount).IsEqualTo(1); - void OnInteractionAdded(object? sender, IInteraction e) + void OnInteractionAdded(object? sender, EventArgs e) { - addedInteractions.Add(e); + interactionCount++; } } diff --git a/Tests/Mockolate.Tests/Verify/MockVerifyTests.cs b/Tests/Mockolate.Tests/Verify/MockVerifyTests.cs index 24f06641..12b73a8f 100644 --- a/Tests/Mockolate.Tests/Verify/MockVerifyTests.cs +++ b/Tests/Mockolate.Tests/Verify/MockVerifyTests.cs @@ -276,7 +276,7 @@ public async Task VerificationResult_ShouldUpdateWhenInteractionsChange() await That(r0).IsTrue().Because("No interaction was performed yet"); await That(r1).IsTrue().Because("One interaction was performed"); - await That(r2).IsTrue().Because("The second interactions did not match"); - await That(r3).IsTrue().Because("The third interactions did again match"); + await That(r2).IsTrue().Because("The second interaction did not match"); + await That(r3).IsTrue().Because("The third interaction did again match"); } }