From 731d6e83b775bfce58ff774ccd2f1ad505a5ceec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Sun, 19 Apr 2026 07:12:02 +0200 Subject: [PATCH 1/4] fix: record interactions even if base class throws --- .../Sources/Sources.MockClass.cs | 36 ++++++------- .../MockTests.ClassTests.MethodTests.cs | 32 +++++------ ...InteractionsTests.ThrowingCallbackTests.cs | 47 ++++++++++++++++ .../InteractionsTests.ThrowingBaseTests.cs | 35 ++++++++++++ .../InteractionsTests.ThrowingBaseTests.cs | 53 +++++++++++++++++++ .../InteractionsTests.ThrowingBaseTests.cs | 35 ++++++++++++ 6 files changed, 204 insertions(+), 34 deletions(-) create mode 100644 Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs create mode 100644 Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs create mode 100644 Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs create mode 100644 Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs index 1d533229..1c9d27a9 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs @@ -1931,6 +1931,24 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb, } } + sb.Append("\t\t\tif (").Append(mockRegistry).Append(".Behavior.SkipInteractionRecording == false)").AppendLine(); + sb.Append("\t\t\t{").AppendLine(); + sb.Append("\t\t\t\t").Append(mockRegistry) + .Append(".RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation"); + if (method.Parameters.Count > 0) + { + sb.Append('<').Append(string.Join(", ", method.Parameters.Select(p => p.ToTypeOrWrapper()))).Append('>'); + } + + sb.Append("(").Append(method.GetUniqueNameString()); + if (method.Parameters.Count > 0) + { + sb.Append(", ").Append(string.Join(", ", method.Parameters.Select(p => $"\"{p.Name}\", {p.ToNameOrWrapper()}"))); + } + + sb.Append("));").AppendLine(); + sb.Append("\t\t\t}").AppendLine(); + if (!isAbstractOrInterface) { if (method.Name.StartsWith("Send", StringComparison.Ordinal) && @@ -2029,24 +2047,6 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb, sb.Append("\t\t\t}").AppendLine(); } - sb.Append("\t\t\tif (").Append(mockRegistry).Append(".Behavior.SkipInteractionRecording == false)").AppendLine(); - sb.Append("\t\t\t{").AppendLine(); - sb.Append("\t\t\t\t").Append(mockRegistry) - .Append(".RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation"); - if (method.Parameters.Count > 0) - { - sb.Append('<').Append(string.Join(", ", method.Parameters.Select(p => p.ToTypeOrWrapper()))).Append('>'); - } - - sb.Append("(").Append(method.GetUniqueNameString()); - if (method.Parameters.Count > 0) - { - sb.Append(", ").Append(string.Join(", ", method.Parameters.Select(p => $"\"{p.Name}\", {p.ToNameOrWrapper()}"))); - } - - sb.Append("));").AppendLine(); - sb.Append("\t\t\t}").AppendLine(); - string displayMethodName = $"{method.ContainingType}.{method.Name}({string.Join(", ", method.Parameters.Select(p => p.Type.DisplayName))})"; sb.Append("\t\t\tif (").Append(methodSetup).Append(" is null && !").Append(hasWrappedResult).Append(" && ").Append(mockRegistry).Append(".Behavior.ThrowWhenNotSetup)").AppendLine(); sb.Append("\t\t\t{").AppendLine(); diff --git a/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.MethodTests.cs b/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.MethodTests.cs index 3f97a1e8..b844f6bc 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.MethodTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.MethodTests.cs @@ -602,6 +602,10 @@ public override void MyMethod1(int index, ref int value1, out bool flag) { flag = default!; } + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + { + this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.MyService.MyMethod1", "index", index, "value1", value1, "flag", flag)); + } if (!(methodSetup?.SkipBaseClass(this.MockRegistry.Behavior) ?? this.MockRegistry.Behavior.SkipBaseClass) && !hasWrappedResult) { base.MyMethod1(index, ref value1, out flag); @@ -625,10 +629,6 @@ public override void MyMethod1(int index, ref int value1, out bool flag) flag = this.MockRegistry.Behavior.DefaultValue.Generate(default(bool)!); } } - if (this.MockRegistry.Behavior.SkipInteractionRecording == false) - { - this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.MyService.MyMethod1", "index", index, "value1", value1, "flag", flag)); - } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.MyService.MyMethod1(int, int, bool)' was invoked without prior setup."); @@ -645,6 +645,10 @@ protected override bool MyMethod2(int index, bool isReadOnly, ref int value1, ou bool hasWrappedResult = false; bool wrappedResult = default!; flag = default!; + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + { + this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.MyService.MyMethod2", "index", index, "isReadOnly", isReadOnly, "value1", value1, "flag", flag)); + } if (!(methodSetup?.SkipBaseClass(this.MockRegistry.Behavior) ?? this.MockRegistry.Behavior.SkipBaseClass)) { wrappedResult = base.MyMethod2(index, isReadOnly, ref value1, out flag); @@ -668,10 +672,6 @@ protected override bool MyMethod2(int index, bool isReadOnly, ref int value1, ou flag = this.MockRegistry.Behavior.DefaultValue.Generate(default(bool)!); } } - if (this.MockRegistry.Behavior.SkipInteractionRecording == false) - { - this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.MyService.MyMethod2", "index", index, "isReadOnly", isReadOnly, "value1", value1, "flag", flag)); - } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.MyService.MyMethod2(int, bool, int, bool)' was invoked without prior setup."); @@ -830,6 +830,10 @@ public void MyMethod1(ref int index) wraps.MyMethod1(ref index); hasWrappedResult = true; } + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + { + this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod1", "index", index)); + } if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.VoidMethodSetup.WithParameterCollection) { if (methodSetup is global::Mockolate.Setup.VoidMethodSetup.WithParameterCollection wpc) @@ -843,10 +847,6 @@ public void MyMethod1(ref int index) { } } - if (this.MockRegistry.Behavior.SkipInteractionRecording == false) - { - this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod1", "index", index)); - } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyService.MyMethod1(int)' was invoked without prior setup."); @@ -870,6 +870,10 @@ public bool MyMethod2(int index, out bool isReadOnly) { isReadOnly = default!; } + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + { + this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod2", "index", index, "isReadOnly", isReadOnly)); + } if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.ReturnMethodSetup.WithParameterCollection) { if (methodSetup is global::Mockolate.Setup.ReturnMethodSetup.WithParameterCollection wpc) @@ -884,10 +888,6 @@ public bool MyMethod2(int index, out bool isReadOnly) isReadOnly = this.MockRegistry.Behavior.DefaultValue.Generate(default(bool)!); } } - if (this.MockRegistry.Behavior.SkipInteractionRecording == false) - { - this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod2", "index", index, "isReadOnly", isReadOnly)); - } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyService.MyMethod2(int, bool)' was invoked without prior setup."); diff --git a/Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs b/Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs new file mode 100644 index 00000000..7d373c12 --- /dev/null +++ b/Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs @@ -0,0 +1,47 @@ +namespace Mockolate.Tests.MockEvents; + +public sealed class InteractionsThrowingCallbackTests +{ + [Fact] + public async Task EventSubscribe_WhenSetupCallbackThrows_ShouldStillRecordSubscription() + { + ThrowingCallbackEventService sut = ThrowingCallbackEventService.CreateMock(); + sut.Mock.Setup.SomeEvent.OnSubscribed + .Do(() => throw new InvalidOperationException("callback throws")); + + void Act() => sut.SomeEvent += Handler; + + await That(Act).Throws(); + await That(sut.Mock.Verify.SomeEvent.Subscribed()).Once(); + + static void Handler(int value) + { + } + } + + [Fact] + public async Task EventUnsubscribe_WhenSetupCallbackThrows_ShouldStillRecordUnsubscription() + { + ThrowingCallbackEventService sut = ThrowingCallbackEventService.CreateMock(); + sut.Mock.Setup.SomeEvent.OnUnsubscribed + .Do(() => throw new InvalidOperationException("callback throws")); + + void Act() => sut.SomeEvent -= Handler; + + await That(Act).Throws(); + await That(sut.Mock.Verify.SomeEvent.Unsubscribed()).Once(); + + static void Handler(int value) + { + } + } + + public delegate void ThrowingCallbackEventHandler(int value); + + public class ThrowingCallbackEventService + { + public virtual event ThrowingCallbackEventHandler? SomeEvent; + + public void Raise(int value) => SomeEvent?.Invoke(value); + } +} diff --git a/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs b/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs new file mode 100644 index 00000000..886d68e7 --- /dev/null +++ b/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs @@ -0,0 +1,35 @@ +namespace Mockolate.Tests.MockIndexers; + +public sealed class InteractionsThrowingBaseTests +{ + [Fact] + public async Task VirtualIndexerGetter_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexer sut = ThrowingBaseIndexer.CreateMock(); + + void Act() => _ = sut[3]; + + await That(Act).Throws(); + await That(sut.Mock.Verify[It.Is(3)].Got()).Once(); + } + + [Fact] + public async Task VirtualIndexerSetter_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexer sut = ThrowingBaseIndexer.CreateMock(); + + void Act() => sut[3] = "value"; + + await That(Act).Throws(); + await That(sut.Mock.Verify[It.Is(3)].Set(It.Is("value"))).Once(); + } + + public class ThrowingBaseIndexer + { + public virtual string this[int key] + { + get => throw new InvalidOperationException("base getter throws"); + set => throw new InvalidOperationException("base setter throws"); + } + } +} diff --git a/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs b/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs new file mode 100644 index 00000000..53f0df57 --- /dev/null +++ b/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs @@ -0,0 +1,53 @@ +namespace Mockolate.Tests.MockMethods; + +public sealed partial class InteractionsTests +{ + public sealed class ThrowingBaseTests + { + [Fact] + public async Task VirtualMethod_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.DoThing(42); + + await That(Act).Throws(); + await That(sut.Mock.Verify.DoThing(It.IsAny())).Once(); + } + + [Fact] + public async Task VirtualMethod_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + try + { + sut.DoThing(7); + } + catch (InvalidOperationException) + { + // expected + } + + await That(sut.Mock.Verify.DoThing(It.Is(7))).Once(); + } + + [Fact] + public async Task VirtualVoidMethod_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.DoVoidThing(); + + await That(Act).Throws(); + await That(sut.Mock.Verify.DoVoidThing()).Once(); + } + + public class ThrowingBaseService + { + public virtual int DoThing(int value) => throw new InvalidOperationException("base throws"); + + public virtual void DoVoidThing() => throw new InvalidOperationException("base throws"); + } + } +} diff --git a/Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs b/Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs new file mode 100644 index 00000000..9e29039e --- /dev/null +++ b/Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs @@ -0,0 +1,35 @@ +namespace Mockolate.Tests.MockProperties; + +public sealed class InteractionsThrowingBaseTests +{ + [Fact] + public async Task VirtualPropertyGetter_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => _ = sut.Value; + + await That(Act).Throws(); + await That(sut.Mock.Verify.Value.Got()).Once(); + } + + [Fact] + public async Task VirtualPropertySetter_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.Value = 42; + + await That(Act).Throws(); + await That(sut.Mock.Verify.Value.Set(It.Is(42))).Once(); + } + + public class ThrowingBaseService + { + public virtual int Value + { + get => throw new InvalidOperationException("base getter throws"); + set => throw new InvalidOperationException("base setter throws"); + } + } +} From 741183a772c3de6f100116894e206119bd8d072b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Sun, 19 Apr 2026 07:23:27 +0200 Subject: [PATCH 2/4] Fix review issue --- .../InteractionsTests.ThrowingBaseTests.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs b/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs index 53f0df57..5e17d3cd 100644 --- a/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs +++ b/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs @@ -15,23 +15,6 @@ public async Task VirtualMethod_WhenBaseThrows_ShouldStillRecordInvocation() await That(sut.Mock.Verify.DoThing(It.IsAny())).Once(); } - [Fact] - public async Task VirtualMethod_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() - { - ThrowingBaseService sut = ThrowingBaseService.CreateMock(); - - try - { - sut.DoThing(7); - } - catch (InvalidOperationException) - { - // expected - } - - await That(sut.Mock.Verify.DoThing(It.Is(7))).Once(); - } - [Fact] public async Task VirtualVoidMethod_WhenBaseThrows_ShouldStillRecordInvocation() { From f0d2c10803dc87e3773cbc16ab8504ef4cb8785b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Sun, 19 Apr 2026 07:43:45 +0200 Subject: [PATCH 3/4] Fix review issues --- ...InteractionsTests.ThrowingCallbackTests.cs | 2 + .../InteractionsTests.ThrowingBaseTests.cs | 71 +++++++++++++------ .../MockIndexers/InteractionsTests.cs | 2 +- .../InteractionsTests.ThrowingBaseTests.cs | 34 +++++++-- .../InteractionsTests.ThrowingBaseTests.cs | 45 ++++++------ .../MockProperties/InteractionsTests.cs | 2 +- 6 files changed, 108 insertions(+), 48 deletions(-) diff --git a/Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs b/Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs index 7d373c12..ec92d0b9 100644 --- a/Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs +++ b/Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs @@ -40,7 +40,9 @@ static void Handler(int value) public class ThrowingCallbackEventService { + #pragma warning disable CA1070 public virtual event ThrowingCallbackEventHandler? SomeEvent; + #pragma warning restore CA1070 public void Raise(int value) => SomeEvent?.Invoke(value); } diff --git a/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs b/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs index 886d68e7..3b9e1219 100644 --- a/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs +++ b/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs @@ -1,35 +1,64 @@ +using Mockolate.Parameters; + namespace Mockolate.Tests.MockIndexers; -public sealed class InteractionsThrowingBaseTests +public sealed partial class InteractionsTests { - [Fact] - public async Task VirtualIndexerGetter_WhenBaseThrows_ShouldStillRecordAccess() + public sealed class ThrowingBaseTests { - ThrowingBaseIndexer sut = ThrowingBaseIndexer.CreateMock(); + [Fact] + public async Task VirtualIndexerGetter_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseIndexer sut = ThrowingBaseIndexer.CreateMock(); + sut.Mock.Setup[It.IsAny().Monitor(out IParameterMonitor values)].Returns("foo"); - void Act() => _ = sut[3]; + void Act() => _ = sut[3]; - await That(Act).Throws(); - await That(sut.Mock.Verify[It.Is(3)].Got()).Once(); - } + await That(Act).Throws(); + await That(values.Values).HasSingle().Which.IsEqualTo(3); + } - [Fact] - public async Task VirtualIndexerSetter_WhenBaseThrows_ShouldStillRecordAccess() - { - ThrowingBaseIndexer sut = ThrowingBaseIndexer.CreateMock(); + [Fact] + public async Task VirtualIndexerGetter_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexer sut = ThrowingBaseIndexer.CreateMock(); - void Act() => sut[3] = "value"; + void Act() => _ = sut[3]; - await That(Act).Throws(); - await That(sut.Mock.Verify[It.Is(3)].Set(It.Is("value"))).Once(); - } + await That(Act).Throws(); + await That(sut.Mock.Verify[It.Is(3)].Got()).Once(); + } - public class ThrowingBaseIndexer - { - public virtual string this[int key] + [Fact] + public async Task VirtualIndexerSetter_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseIndexer sut = ThrowingBaseIndexer.CreateMock(); + sut.Mock.Setup[It.IsAny().Monitor(out IParameterMonitor values)].Returns("foo"); + + void Act() => sut[3] = "value"; + + await That(Act).Throws(); + await That(values.Values).HasSingle().Which.IsEqualTo(3); + } + + [Fact] + public async Task VirtualIndexerSetter_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexer sut = ThrowingBaseIndexer.CreateMock(); + + void Act() => sut[3] = "value"; + + await That(Act).Throws(); + await That(sut.Mock.Verify[It.Is(3)].Set(It.Is("value"))).Once(); + } + + public class ThrowingBaseIndexer { - get => throw new InvalidOperationException("base getter throws"); - set => throw new InvalidOperationException("base setter throws"); + public virtual string this[int key] + { + get => throw new InvalidOperationException("base getter throws"); + set => throw new InvalidOperationException("base setter throws"); + } } } } diff --git a/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.cs b/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.cs index 94c07a95..e9554fdd 100644 --- a/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.cs +++ b/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.cs @@ -3,7 +3,7 @@ namespace Mockolate.Tests.MockIndexers; -public sealed class InteractionsTests +public sealed partial class InteractionsTests { [Fact] public async Task IndexerGetterAccess1_ToString_ShouldReturnExpectedValue() diff --git a/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs b/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs index 5e17d3cd..dae873bf 100644 --- a/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs +++ b/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs @@ -1,9 +1,23 @@ +using Mockolate.Parameters; + namespace Mockolate.Tests.MockMethods; public sealed partial class InteractionsTests { public sealed class ThrowingBaseTests { + [Fact] + public async Task VirtualMethod_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + sut.Mock.Setup.DoThing(It.IsAny().Monitor(out IParameterMonitor values)).Returns(1); + + void Act() => sut.DoThing(42); + + await That(Act).Throws(); + await That(values.Values).HasSingle().Which.IsEqualTo(42); + } + [Fact] public async Task VirtualMethod_WhenBaseThrows_ShouldStillRecordInvocation() { @@ -12,7 +26,19 @@ public async Task VirtualMethod_WhenBaseThrows_ShouldStillRecordInvocation() void Act() => sut.DoThing(42); await That(Act).Throws(); - await That(sut.Mock.Verify.DoThing(It.IsAny())).Once(); + await That(sut.Mock.Verify.DoThing(42)).Once(); + } + + [Fact] + public async Task VirtualVoidMethod_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + sut.Mock.Setup.DoVoidThing(It.IsAny().Monitor(out IParameterMonitor values)).DoesNotThrow(); + + void Act() => sut.DoVoidThing(42); + + await That(Act).Throws(); + await That(values.Values).HasSingle().Which.IsEqualTo(42); } [Fact] @@ -20,17 +46,17 @@ public async Task VirtualVoidMethod_WhenBaseThrows_ShouldStillRecordInvocation() { ThrowingBaseService sut = ThrowingBaseService.CreateMock(); - void Act() => sut.DoVoidThing(); + void Act() => sut.DoVoidThing(1); await That(Act).Throws(); - await That(sut.Mock.Verify.DoVoidThing()).Once(); + await That(sut.Mock.Verify.DoVoidThing(1)).Once(); } public class ThrowingBaseService { public virtual int DoThing(int value) => throw new InvalidOperationException("base throws"); - public virtual void DoVoidThing() => throw new InvalidOperationException("base throws"); + public virtual void DoVoidThing(int value) => throw new InvalidOperationException("base throws"); } } } diff --git a/Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs b/Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs index 9e29039e..367be45c 100644 --- a/Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs +++ b/Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs @@ -1,35 +1,38 @@ namespace Mockolate.Tests.MockProperties; -public sealed class InteractionsThrowingBaseTests +public sealed partial class InteractionsTests { - [Fact] - public async Task VirtualPropertyGetter_WhenBaseThrows_ShouldStillRecordAccess() + public sealed class ThrowingBaseTests { - ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + [Fact] + public async Task VirtualPropertyGetter_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); - void Act() => _ = sut.Value; + void Act() => _ = sut.Value; - await That(Act).Throws(); - await That(sut.Mock.Verify.Value.Got()).Once(); - } + await That(Act).Throws(); + await That(sut.Mock.Verify.Value.Got()).Once(); + } - [Fact] - public async Task VirtualPropertySetter_WhenBaseThrows_ShouldStillRecordAccess() - { - ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + [Fact] + public async Task VirtualPropertySetter_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); - void Act() => sut.Value = 42; + void Act() => sut.Value = 42; - await That(Act).Throws(); - await That(sut.Mock.Verify.Value.Set(It.Is(42))).Once(); - } + await That(Act).Throws(); + await That(sut.Mock.Verify.Value.Set(It.Is(42))).Once(); + } - public class ThrowingBaseService - { - public virtual int Value + public class ThrowingBaseService { - get => throw new InvalidOperationException("base getter throws"); - set => throw new InvalidOperationException("base setter throws"); + public virtual int Value + { + get => throw new InvalidOperationException("base getter throws"); + set => throw new InvalidOperationException("base setter throws"); + } } } } diff --git a/Tests/Mockolate.Tests/MockProperties/InteractionsTests.cs b/Tests/Mockolate.Tests/MockProperties/InteractionsTests.cs index 1c6220b4..47415d7c 100644 --- a/Tests/Mockolate.Tests/MockProperties/InteractionsTests.cs +++ b/Tests/Mockolate.Tests/MockProperties/InteractionsTests.cs @@ -5,7 +5,7 @@ namespace Mockolate.Tests.MockProperties; -public sealed class InteractionsTests +public sealed partial class InteractionsTests { [Fact] public async Task MockGot_WhenNameDoesNotMatch_ShouldReturnNever() From c7583fecb26f1e8979de0c3f335d5f3dd6fefa78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Sun, 19 Apr 2026 08:05:22 +0200 Subject: [PATCH 4/4] Also execute callbacks even when base class throws --- .../Sources/Sources.MockClass.cs | 141 ++++---- Source/Mockolate/MockRegistry.Interactions.cs | 33 +- .../GeneralTests.cs | 18 +- .../MockTests.ClassTests.MethodTests.cs | 308 +++++++++------- .../MockTests.cs | 18 +- ...InteractionsTests.ThrowingCallbackTests.cs | 69 ++-- .../MockEvents/InteractionsTests.cs | 2 +- .../InteractionsTests.ThrowingBaseTests.cs | 300 ++++++++++++++-- .../InteractionsTests.ThrowingBaseTests.cs | 328 +++++++++++++++++- .../InteractionsTests.ThrowingBaseTests.cs | 26 ++ 10 files changed, 968 insertions(+), 275 deletions(-) diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs index 1c9d27a9..e18239d3 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs @@ -1888,42 +1888,7 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb, .AppendLine(); } - if (supportsWrapping) - { - sb.Append("\t\t\tif (").Append(mockRegistry).Append(".Wraps is ").Append(className) - .Append(" wraps)").AppendLine(); - sb.Append("\t\t\t{").AppendLine(); - if (method.ReturnType != Type.Void) - { - sb.Append("\t\t\t\t").Append(wrappedResult).Append(" = wraps").Append(".") - .Append(method.Name).Append('(') - .Append(FormatMethodParametersWithRefKind(method.Parameters)) - .Append(");").AppendLine(); - sb.Append("\t\t\t\t").Append(hasWrappedResult).Append(" = true;").AppendLine(); - } - else - { - sb.Append("\t\t\t\twraps").Append(".") - .Append(method.Name).Append('(') - .Append(FormatMethodParametersWithRefKind(method.Parameters)) - .Append(");").AppendLine(); - sb.Append("\t\t\t\t").Append(hasWrappedResult).Append(" = true;").AppendLine(); - } - - sb.Append("\t\t\t}").AppendLine(); - if (hasOutParams) - { - sb.Append("\t\t\telse").AppendLine(); - sb.Append("\t\t\t{").AppendLine(); - foreach (MethodParameter parameter in method.Parameters.Where(p => p.RefKind == RefKind.Out)) - { - sb.Append("\t\t\t\t").Append(parameter.Name).Append(" = default!;").AppendLine(); - } - - sb.Append("\t\t\t}").AppendLine(); - } - } - else if (hasOutParams) + if (hasOutParams) { foreach (MethodParameter parameter in method.Parameters.Where(p => p.RefKind == RefKind.Out)) { @@ -1949,30 +1914,58 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb, sb.Append("));").AppendLine(); sb.Append("\t\t\t}").AppendLine(); + sb.Append("\t\t\ttry").AppendLine(); + sb.Append("\t\t\t{").AppendLine(); + + if (supportsWrapping) + { + sb.Append("\t\t\t\tif (").Append(mockRegistry).Append(".Wraps is ").Append(className) + .Append(" wraps)").AppendLine(); + sb.Append("\t\t\t\t{").AppendLine(); + if (method.ReturnType != Type.Void) + { + sb.Append("\t\t\t\t\t").Append(wrappedResult).Append(" = wraps").Append(".") + .Append(method.Name).Append('(') + .Append(FormatMethodParametersWithRefKind(method.Parameters)) + .Append(");").AppendLine(); + sb.Append("\t\t\t\t\t").Append(hasWrappedResult).Append(" = true;").AppendLine(); + } + else + { + sb.Append("\t\t\t\t\twraps").Append(".") + .Append(method.Name).Append('(') + .Append(FormatMethodParametersWithRefKind(method.Parameters)) + .Append(");").AppendLine(); + sb.Append("\t\t\t\t\t").Append(hasWrappedResult).Append(" = true;").AppendLine(); + } + + sb.Append("\t\t\t\t}").AppendLine(); + } + if (!isAbstractOrInterface) { if (method.Name.StartsWith("Send", StringComparison.Ordinal) && @class is { ClassFullName: "global::System.Net.Http.HttpClient", }) { - sb.Append("\t\t\t#if NETFRAMEWORK").AppendLine(); + sb.Append("\t\t\t\t#if NETFRAMEWORK").AppendLine(); sb.Append( - "\t\t\t// Persist the HttpContent, because it gets automatically disposed on .NET Framework") + "\t\t\t\t// Persist the HttpContent, because it gets automatically disposed on .NET Framework") .AppendLine(); - sb.Append("\t\t\tif (request.Content != null)").AppendLine(); - sb.Append("\t\t\t{").AppendLine(); + sb.Append("\t\t\t\tif (request.Content != null)").AppendLine(); + sb.Append("\t\t\t\t{").AppendLine(); sb.Append( - "\t\t\t\tvar stream = request.Content.ReadAsStreamAsync().ConfigureAwait(false).GetAwaiter().GetResult();") + "\t\t\t\t\tvar stream = request.Content.ReadAsStreamAsync().ConfigureAwait(false).GetAwaiter().GetResult();") .AppendLine(); - sb.Append("\t\t\t\tusing global::System.IO.MemoryStream ms = new();").AppendLine(); - sb.Append("\t\t\t\tstream.CopyTo(ms);").AppendLine(); - sb.Append("\t\t\t\tbyte[] bytes = ms.ToArray();").AppendLine(); - sb.Append("\t\t\t\tstream.Position = 0L;").AppendLine(); - sb.Append("\t\t\t\trequest.Properties.Add(\"Mockolate:HttpContent\", bytes);").AppendLine(); - sb.Append("\t\t\t}").AppendLine(); - sb.Append("\t\t\t#endif").AppendLine(); + sb.Append("\t\t\t\t\tusing global::System.IO.MemoryStream ms = new();").AppendLine(); + sb.Append("\t\t\t\t\tstream.CopyTo(ms);").AppendLine(); + sb.Append("\t\t\t\t\tbyte[] bytes = ms.ToArray();").AppendLine(); + sb.Append("\t\t\t\t\tstream.Position = 0L;").AppendLine(); + sb.Append("\t\t\t\t\trequest.Properties.Add(\"Mockolate:HttpContent\", bytes);").AppendLine(); + sb.Append("\t\t\t\t}").AppendLine(); + sb.Append("\t\t\t\t#endif").AppendLine(); } - sb.Append("\t\t\tif (!(").Append(methodSetup).Append("?.SkipBaseClass(").Append(mockRegistry) + sb.Append("\t\t\t\tif (!(").Append(methodSetup).Append("?.SkipBaseClass(").Append(mockRegistry) .Append(".Behavior) ?? ").Append(mockRegistry).Append(".Behavior.SkipBaseClass)"); if (supportsWrapping) { @@ -1980,8 +1973,8 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb, } sb.Append(')').AppendLine(); - sb.Append("\t\t\t{").AppendLine(); - sb.Append("\t\t\t\t"); + sb.Append("\t\t\t\t{").AppendLine(); + sb.Append("\t\t\t\t\t"); if (method.ReturnType != Type.Void) { sb.Append(wrappedResult).Append(" = "); @@ -1990,71 +1983,75 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb, sb.Append("base.").Append(method.Name).Append('(') .Append(FormatMethodParametersWithRefKind(method.Parameters)) .Append(");").AppendLine(); - sb.Append("\t\t\t\t").Append(hasWrappedResult).Append(" = true;").AppendLine(); - sb.Append("\t\t\t}").AppendLine(); + sb.Append("\t\t\t\t\t").Append(hasWrappedResult).Append(" = true;").AppendLine(); + sb.Append("\t\t\t\t}").AppendLine(); } if (hasOutParams || hasRefParams) { - sb.Append("\t\t\tif (!").Append(hasWrappedResult).Append(" || ").Append(methodSetup).Append(" is ").Append(methodSetupType) + sb.Append("\t\t\t\tif (!").Append(hasWrappedResult).Append(" || ").Append(methodSetup).Append(" is ").Append(methodSetupType) .Append(".WithParameterCollection)") .AppendLine(); - sb.Append("\t\t\t{").AppendLine(); - sb.Append("\t\t\t\tif (").Append(methodSetup).Append(" is ").Append(methodSetupType) - .Append(".WithParameterCollection ").Append(wpc).Append(')').AppendLine(); sb.Append("\t\t\t\t{").AppendLine(); + sb.Append("\t\t\t\t\tif (").Append(methodSetup).Append(" is ").Append(methodSetupType) + .Append(".WithParameterCollection ").Append(wpc).Append(')').AppendLine(); + sb.Append("\t\t\t\t\t{").AppendLine(); int parameterIndex = 0; foreach (MethodParameter parameter in method.Parameters) { parameterIndex++; if (parameter.RefKind == RefKind.Out) { - sb.Append("\t\t\t\t\tif (").Append(wpc).Append(".Parameter").Append(parameterIndex) + sb.Append("\t\t\t\t\t\tif (").Append(wpc).Append(".Parameter").Append(parameterIndex) .Append(" is not global::Mockolate.Parameters.IOutParameter<") .Append(parameter.Type.ToTypeOrWrapper()).Append("> outParam").Append(parameterIndex) .Append(" || !outParam").Append(parameterIndex).Append(".TryGetValue(out ") .Append(parameter.Name).Append("))").AppendLine(); - sb.Append("\t\t\t\t\t{").AppendLine(); - sb.Append("\t\t\t\t\t\t").Append(parameter.Name).Append(" = ") + sb.Append("\t\t\t\t\t\t{").AppendLine(); + sb.Append("\t\t\t\t\t\t\t").Append(parameter.Name).Append(" = ") .AppendDefaultValueGeneratorFor(parameter.Type, $"{mockRegistry}.Behavior.DefaultValue") .Append(';').AppendLine(); - sb.Append("\t\t\t\t\t}").AppendLine(); + sb.Append("\t\t\t\t\t\t}").AppendLine(); } else if (parameter.RefKind == RefKind.Ref) { - sb.Append("\t\t\t\t\tif (").Append(wpc).Append(".Parameter").Append(parameterIndex) + sb.Append("\t\t\t\t\t\tif (").Append(wpc).Append(".Parameter").Append(parameterIndex) .Append(" is global::Mockolate.Parameters.IRefParameter<") .Append(parameter.Type.ToTypeOrWrapper()).Append("> refParam").Append(parameterIndex) .Append(")").AppendLine(); - sb.Append("\t\t\t\t\t{").AppendLine(); - sb.Append("\t\t\t\t\t\t").Append(parameter.Name).Append(" = refParam").Append(parameterIndex) + sb.Append("\t\t\t\t\t\t{").AppendLine(); + sb.Append("\t\t\t\t\t\t\t").Append(parameter.Name).Append(" = refParam").Append(parameterIndex) .Append(".GetValue(").Append(parameter.Name).Append(");").AppendLine(); - sb.Append("\t\t\t\t\t}").AppendLine(); + sb.Append("\t\t\t\t\t\t}").AppendLine(); } } - sb.Append("\t\t\t\t}").AppendLine(); - sb.Append("\t\t\t\telse").AppendLine(); - sb.Append("\t\t\t\t{").AppendLine(); + sb.Append("\t\t\t\t\t}").AppendLine(); + sb.Append("\t\t\t\t\telse").AppendLine(); + sb.Append("\t\t\t\t\t{").AppendLine(); foreach (MethodParameter parameter in method.Parameters.Where(p => p.RefKind == RefKind.Out)) { - sb.Append("\t\t\t\t\t").Append(parameter.Name).Append(" = ") + sb.Append("\t\t\t\t\t\t").Append(parameter.Name).Append(" = ") .AppendDefaultValueGeneratorFor(parameter.Type, $"{mockRegistry}.Behavior.DefaultValue").Append(';') .AppendLine(); } + sb.Append("\t\t\t\t\t}").AppendLine(); sb.Append("\t\t\t\t}").AppendLine(); - sb.Append("\t\t\t}").AppendLine(); } + sb.Append("\t\t\t}").AppendLine(); + sb.Append("\t\t\tfinally").AppendLine(); + sb.Append("\t\t\t{").AppendLine(); + AppendTriggerCallbacks(sb, "\t\t\t\t", methodSetup, method.Parameters); + sb.Append("\t\t\t}").AppendLine(); + string displayMethodName = $"{method.ContainingType}.{method.Name}({string.Join(", ", method.Parameters.Select(p => p.Type.DisplayName))})"; sb.Append("\t\t\tif (").Append(methodSetup).Append(" is null && !").Append(hasWrappedResult).Append(" && ").Append(mockRegistry).Append(".Behavior.ThrowWhenNotSetup)").AppendLine(); sb.Append("\t\t\t{").AppendLine(); sb.Append("\t\t\t\tthrow new global::Mockolate.Exceptions.MockNotSetupException(\"The method '").Append(displayMethodName).Append("' was invoked without prior setup.\");").AppendLine(); sb.Append("\t\t\t}").AppendLine(); - AppendTriggerCallbacks(sb, "\t\t\t", methodSetup, method.Parameters); - if (method.ReturnType != Type.Void) { sb.Append("\t\t\tif (").Append(methodSetup).Append("?.HasReturnCallbacks != true && ").Append(hasWrappedResult).Append(")").AppendLine(); diff --git a/Source/Mockolate/MockRegistry.Interactions.cs b/Source/Mockolate/MockRegistry.Interactions.cs index 97fc0081..fd24b58d 100644 --- a/Source/Mockolate/MockRegistry.Interactions.cs +++ b/Source/Mockolate/MockRegistry.Interactions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Runtime.ExceptionServices; using Mockolate.Exceptions; using Mockolate.Interactions; using Mockolate.Setup; @@ -252,12 +253,36 @@ public TResult GetProperty(string propertyName, Func defaultVa new PropertyGetterAccess(propertyName)); } - PropertySetup matchingSetup = ResolvePropertySetup( - propertyName, defaultValueGenerator, baseValueAccessor, - baseValueAccessor is not null); + PropertySetup matchingSetup; + if (baseValueAccessor is null) + { + matchingSetup = ResolvePropertySetup(propertyName, defaultValueGenerator, null, false); - return ((IInteractivePropertySetup)matchingSetup).InvokeGetter(interaction, Behavior, + return ((IInteractivePropertySetup)matchingSetup).InvokeGetter(interaction, Behavior, + defaultValueGenerator); + } + ExceptionDispatchInfo? capturedBaseException = null; + Func safeBaseValueAccessor = () => + { + try + { + return baseValueAccessor(); + } + catch (Exception ex) + { + capturedBaseException = ExceptionDispatchInfo.Capture(ex); + return default!; + } + }; + + matchingSetup = ResolvePropertySetup( + propertyName, defaultValueGenerator, safeBaseValueAccessor, true); + + TResult result = ((IInteractivePropertySetup)matchingSetup).InvokeGetter(interaction, Behavior, defaultValueGenerator); + + capturedBaseException?.Throw(); + return result; } /// diff --git a/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs b/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs index 44ca980f..0f86834d 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs @@ -797,20 +797,26 @@ public string MyMethod(string message) var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.IMyService.MyMethod", m => m.Matches("message", message)); bool hasWrappedResult = false; string wrappedResult = default!; - if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) - { - wrappedResult = wraps.MyMethod(message); - hasWrappedResult = true; - } if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod", "message", message)); } + try + { + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) + { + wrappedResult = wraps.MyMethod(message); + hasWrappedResult = true; + } + } + finally + { + methodSetup?.TriggerCallbacks(message); + } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyService.MyMethod(string)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(message); if (methodSetup?.HasReturnCallbacks != true && hasWrappedResult) { return wrappedResult; diff --git a/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.MethodTests.cs b/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.MethodTests.cs index b844f6bc..128fb914 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.MethodTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.MethodTests.cs @@ -342,20 +342,26 @@ public bool MyMethod1(int index) var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.IMyService.MyMethod1", m => m.Matches("index", index)); bool hasWrappedResult = false; bool wrappedResult = default!; - if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) - { - wrappedResult = wraps.MyMethod1(index); - hasWrappedResult = true; - } if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod1", "index", index)); } + try + { + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) + { + wrappedResult = wraps.MyMethod1(index); + hasWrappedResult = true; + } + } + finally + { + methodSetup?.TriggerCallbacks(index); + } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyService.MyMethod1(int)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(index); if (methodSetup?.HasReturnCallbacks != true && hasWrappedResult) { return wrappedResult; @@ -369,20 +375,26 @@ public void MyMethod2(int index, bool isReadOnly) { var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.IMyService.MyMethod2", m => m.Matches("index", index, "isReadOnly", isReadOnly)); bool hasWrappedResult = false; - if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) - { - wraps.MyMethod2(index, isReadOnly); - hasWrappedResult = true; - } if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod2", "index", index, "isReadOnly", isReadOnly)); } + try + { + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) + { + wraps.MyMethod2(index, isReadOnly); + hasWrappedResult = true; + } + } + finally + { + methodSetup?.TriggerCallbacks(index, isReadOnly); + } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyService.MyMethod2(int, bool)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(index, isReadOnly); } """).IgnoringNewlineStyle(); } @@ -433,20 +445,26 @@ public int MyDirectMethod(int value) var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.IMyService.MyDirectMethod", m => m.Matches("value", value)); bool hasWrappedResult = false; int wrappedResult = default!; - if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) - { - wrappedResult = wraps.MyDirectMethod(value); - hasWrappedResult = true; - } if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyDirectMethod", "value", value)); } + try + { + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) + { + wrappedResult = wraps.MyDirectMethod(value); + hasWrappedResult = true; + } + } + finally + { + methodSetup?.TriggerCallbacks(value); + } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyService.MyDirectMethod(int)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(value); if (methodSetup?.HasReturnCallbacks != true && hasWrappedResult) { return wrappedResult; @@ -461,20 +479,26 @@ public int MyBaseMethod1(int value) var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.IMyServiceBase1.MyBaseMethod1", m => m.Matches("value", value)); bool hasWrappedResult = false; int wrappedResult = default!; - if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) - { - wrappedResult = wraps.MyBaseMethod1(value); - hasWrappedResult = true; - } if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyServiceBase1.MyBaseMethod1", "value", value)); } + try + { + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) + { + wrappedResult = wraps.MyBaseMethod1(value); + hasWrappedResult = true; + } + } + finally + { + methodSetup?.TriggerCallbacks(value); + } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyServiceBase1.MyBaseMethod1(int)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(value); if (methodSetup?.HasReturnCallbacks != true && hasWrappedResult) { return wrappedResult; @@ -489,20 +513,26 @@ public int MyBaseMethod2(int value) var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.IMyServiceBase2.MyBaseMethod2", m => m.Matches("value", value)); bool hasWrappedResult = false; int wrappedResult = default!; - if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) - { - wrappedResult = wraps.MyBaseMethod2(value); - hasWrappedResult = true; - } if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyServiceBase2.MyBaseMethod2", "value", value)); } + try + { + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) + { + wrappedResult = wraps.MyBaseMethod2(value); + hasWrappedResult = true; + } + } + finally + { + methodSetup?.TriggerCallbacks(value); + } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyServiceBase2.MyBaseMethod2(int)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(value); if (methodSetup?.HasReturnCallbacks != true && hasWrappedResult) { return wrappedResult; @@ -517,20 +547,26 @@ public int MyBaseMethod3(int value) var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.IMyServiceBase3.MyBaseMethod3", m => m.Matches("value", value)); bool hasWrappedResult = false; int wrappedResult = default!; - if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) - { - wrappedResult = wraps.MyBaseMethod3(value); - hasWrappedResult = true; - } if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyServiceBase3.MyBaseMethod3", "value", value)); } + try + { + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) + { + wrappedResult = wraps.MyBaseMethod3(value); + hasWrappedResult = true; + } + } + finally + { + methodSetup?.TriggerCallbacks(value); + } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyServiceBase3.MyBaseMethod3(int)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(value); if (methodSetup?.HasReturnCallbacks != true && hasWrappedResult) { return wrappedResult; @@ -593,47 +629,50 @@ public override void MyMethod1(int index, ref int value1, out bool flag) var ref_value1 = value1; var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.MyService.MyMethod1", m => m.Matches("index", index, "value1", ref_value1, "flag", default)); bool hasWrappedResult = false; - if (this.MockRegistry.Wraps is global::MyCode.MyService wraps) - { - wraps.MyMethod1(index, ref value1, out flag); - hasWrappedResult = true; - } - else - { - flag = default!; - } + flag = default!; if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.MyService.MyMethod1", "index", index, "value1", value1, "flag", flag)); } - if (!(methodSetup?.SkipBaseClass(this.MockRegistry.Behavior) ?? this.MockRegistry.Behavior.SkipBaseClass) && !hasWrappedResult) + try { - base.MyMethod1(index, ref value1, out flag); - hasWrappedResult = true; - } - if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.VoidMethodSetup.WithParameterCollection) - { - if (methodSetup is global::Mockolate.Setup.VoidMethodSetup.WithParameterCollection wpc) + if (this.MockRegistry.Wraps is global::MyCode.MyService wraps) + { + wraps.MyMethod1(index, ref value1, out flag); + hasWrappedResult = true; + } + if (!(methodSetup?.SkipBaseClass(this.MockRegistry.Behavior) ?? this.MockRegistry.Behavior.SkipBaseClass) && !hasWrappedResult) { - if (wpc.Parameter2 is global::Mockolate.Parameters.IRefParameter refParam2) + base.MyMethod1(index, ref value1, out flag); + hasWrappedResult = true; + } + if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.VoidMethodSetup.WithParameterCollection) + { + if (methodSetup is global::Mockolate.Setup.VoidMethodSetup.WithParameterCollection wpc) { - value1 = refParam2.GetValue(value1); + if (wpc.Parameter2 is global::Mockolate.Parameters.IRefParameter refParam2) + { + value1 = refParam2.GetValue(value1); + } + if (wpc.Parameter3 is not global::Mockolate.Parameters.IOutParameter outParam3 || !outParam3.TryGetValue(out flag)) + { + flag = this.MockRegistry.Behavior.DefaultValue.Generate(default(bool)!); + } } - if (wpc.Parameter3 is not global::Mockolate.Parameters.IOutParameter outParam3 || !outParam3.TryGetValue(out flag)) + else { flag = this.MockRegistry.Behavior.DefaultValue.Generate(default(bool)!); } } - else - { - flag = this.MockRegistry.Behavior.DefaultValue.Generate(default(bool)!); - } + } + finally + { + methodSetup?.TriggerCallbacks(index, value1, flag); } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.MyService.MyMethod1(int, int, bool)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(index, value1, flag); } """).IgnoringNewlineStyle().And .Contains(""" @@ -649,34 +688,40 @@ protected override bool MyMethod2(int index, bool isReadOnly, ref int value1, ou { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.MyService.MyMethod2", "index", index, "isReadOnly", isReadOnly, "value1", value1, "flag", flag)); } - if (!(methodSetup?.SkipBaseClass(this.MockRegistry.Behavior) ?? this.MockRegistry.Behavior.SkipBaseClass)) - { - wrappedResult = base.MyMethod2(index, isReadOnly, ref value1, out flag); - hasWrappedResult = true; - } - if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.ReturnMethodSetup.WithParameterCollection) + try { - if (methodSetup is global::Mockolate.Setup.ReturnMethodSetup.WithParameterCollection wpc) + if (!(methodSetup?.SkipBaseClass(this.MockRegistry.Behavior) ?? this.MockRegistry.Behavior.SkipBaseClass)) + { + wrappedResult = base.MyMethod2(index, isReadOnly, ref value1, out flag); + hasWrappedResult = true; + } + if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.ReturnMethodSetup.WithParameterCollection) { - if (wpc.Parameter3 is global::Mockolate.Parameters.IRefParameter refParam3) + if (methodSetup is global::Mockolate.Setup.ReturnMethodSetup.WithParameterCollection wpc) { - value1 = refParam3.GetValue(value1); + if (wpc.Parameter3 is global::Mockolate.Parameters.IRefParameter refParam3) + { + value1 = refParam3.GetValue(value1); + } + if (wpc.Parameter4 is not global::Mockolate.Parameters.IOutParameter outParam4 || !outParam4.TryGetValue(out flag)) + { + flag = this.MockRegistry.Behavior.DefaultValue.Generate(default(bool)!); + } } - if (wpc.Parameter4 is not global::Mockolate.Parameters.IOutParameter outParam4 || !outParam4.TryGetValue(out flag)) + else { flag = this.MockRegistry.Behavior.DefaultValue.Generate(default(bool)!); } } - else - { - flag = this.MockRegistry.Behavior.DefaultValue.Generate(default(bool)!); - } + } + finally + { + methodSetup?.TriggerCallbacks(index, isReadOnly, value1, flag); } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.MyService.MyMethod2(int, bool, int, bool)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(index, isReadOnly, value1, flag); if (methodSetup?.HasReturnCallbacks != true && hasWrappedResult) { return wrappedResult; @@ -696,11 +741,17 @@ protected override bool MyMethod2(int index, bool isReadOnly, ref int value1, ou { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyOtherService.SomeOtherMethod")); } + try + { + } + finally + { + methodSetup?.TriggerCallbacks(); + } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyOtherService.SomeOtherMethod()' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(); """).IgnoringNewlineStyle(); } @@ -825,33 +876,39 @@ public void MyMethod1(ref int index) var ref_index = index; var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.IMyService.MyMethod1", m => m.Matches("index", ref_index)); bool hasWrappedResult = false; - if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) - { - wraps.MyMethod1(ref index); - hasWrappedResult = true; - } if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod1", "index", index)); } - if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.VoidMethodSetup.WithParameterCollection) + try { - if (methodSetup is global::Mockolate.Setup.VoidMethodSetup.WithParameterCollection wpc) + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) { - if (wpc.Parameter1 is global::Mockolate.Parameters.IRefParameter refParam1) - { - index = refParam1.GetValue(index); - } + wraps.MyMethod1(ref index); + hasWrappedResult = true; } - else + if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.VoidMethodSetup.WithParameterCollection) { + if (methodSetup is global::Mockolate.Setup.VoidMethodSetup.WithParameterCollection wpc) + { + if (wpc.Parameter1 is global::Mockolate.Parameters.IRefParameter refParam1) + { + index = refParam1.GetValue(index); + } + } + else + { + } } } + finally + { + methodSetup?.TriggerCallbacks(index); + } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyService.MyMethod1(int)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(index); } """).IgnoringNewlineStyle().And .Contains(""" @@ -861,38 +918,41 @@ public bool MyMethod2(int index, out bool isReadOnly) var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.IMyService.MyMethod2", m => m.Matches("index", index, "isReadOnly", default)); bool hasWrappedResult = false; bool wrappedResult = default!; - if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) - { - wrappedResult = wraps.MyMethod2(index, out isReadOnly); - hasWrappedResult = true; - } - else - { - isReadOnly = default!; - } + isReadOnly = default!; if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod2", "index", index, "isReadOnly", isReadOnly)); } - if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.ReturnMethodSetup.WithParameterCollection) + try { - if (methodSetup is global::Mockolate.Setup.ReturnMethodSetup.WithParameterCollection wpc) + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) { - if (wpc.Parameter2 is not global::Mockolate.Parameters.IOutParameter outParam2 || !outParam2.TryGetValue(out isReadOnly)) + wrappedResult = wraps.MyMethod2(index, out isReadOnly); + hasWrappedResult = true; + } + if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.ReturnMethodSetup.WithParameterCollection) + { + if (methodSetup is global::Mockolate.Setup.ReturnMethodSetup.WithParameterCollection wpc) + { + if (wpc.Parameter2 is not global::Mockolate.Parameters.IOutParameter outParam2 || !outParam2.TryGetValue(out isReadOnly)) + { + isReadOnly = this.MockRegistry.Behavior.DefaultValue.Generate(default(bool)!); + } + } + else { isReadOnly = this.MockRegistry.Behavior.DefaultValue.Generate(default(bool)!); } } - else - { - isReadOnly = this.MockRegistry.Behavior.DefaultValue.Generate(default(bool)!); - } + } + finally + { + methodSetup?.TriggerCallbacks(index, isReadOnly); } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyService.MyMethod2(int, bool)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(index, isReadOnly); if (methodSetup?.HasReturnCallbacks != true && hasWrappedResult) { return wrappedResult; @@ -907,20 +967,26 @@ public void MyMethod3(in global::MyCode.MyReadonlyStruct p1) var ref_p1 = p1; var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.IMyService.MyMethod3", m => m.Matches("p1", ref_p1)); bool hasWrappedResult = false; - if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) - { - wraps.MyMethod3(in p1); - hasWrappedResult = true; - } if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod3", "p1", p1)); } + try + { + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) + { + wraps.MyMethod3(in p1); + hasWrappedResult = true; + } + } + finally + { + methodSetup?.TriggerCallbacks(p1); + } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyService.MyMethod3(MyReadonlyStruct)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(p1); } """).IgnoringNewlineStyle().And .Contains(""" @@ -930,20 +996,26 @@ public void MyMethod4(ref readonly global::MyCode.MyReadonlyStruct p1) var ref_p1 = p1; var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.IMyService.MyMethod4", m => m.Matches("p1", ref_p1)); bool hasWrappedResult = false; - if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) - { - wraps.MyMethod4(in p1); - hasWrappedResult = true; - } if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod4", "p1", p1)); } + try + { + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) + { + wraps.MyMethod4(in p1); + hasWrappedResult = true; + } + } + finally + { + methodSetup?.TriggerCallbacks(p1); + } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyService.MyMethod4(MyReadonlyStruct)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(p1); } """).IgnoringNewlineStyle(); } diff --git a/Tests/Mockolate.SourceGenerators.Tests/MockTests.cs b/Tests/Mockolate.SourceGenerators.Tests/MockTests.cs index 3ec20526..7af21f76 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/MockTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/MockTests.cs @@ -612,20 +612,26 @@ public void MyMethod(object v1, bool v2, string v3, char v4, byte v5, sbyte v6, { var methodSetup = this.MockRegistry.GetMethodSetup>("global::MyCode.IMyService.MyMethod", m => m.Matches("v1", v1, "v2", v2, "v3", v3, "v4", v4, "v5", v5, "v6", v6, "v7", v7, "v8", v8, "v9", v9, "v10", v10, "v11", v11, "v12", v12, "v13", v13, "v14", v14, "v15", v15)); bool hasWrappedResult = false; - if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) - { - wraps.MyMethod(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); - hasWrappedResult = true; - } if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod", "v1", v1, "v2", v2, "v3", v3, "v4", v4, "v5", v5, "v6", v6, "v7", v7, "v8", v8, "v9", v9, "v10", v10, "v11", v11, "v12", v12, "v13", v13, "v14", v14, "v15", v15)); } + try + { + if (this.MockRegistry.Wraps is global::MyCode.IMyService wraps) + { + wraps.MyMethod(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); + hasWrappedResult = true; + } + } + finally + { + methodSetup?.TriggerCallbacks(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); + } if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) { throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::MyCode.IMyService.MyMethod(object, bool, string, char, byte, sbyte, short, ushort, int, uint, long, ulong, float, double, decimal)' was invoked without prior setup."); } - methodSetup?.TriggerCallbacks(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); } """).IgnoringNewlineStyle(); } diff --git a/Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs b/Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs index ec92d0b9..17368610 100644 --- a/Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs +++ b/Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs @@ -1,49 +1,54 @@ namespace Mockolate.Tests.MockEvents; -public sealed class InteractionsThrowingCallbackTests +public sealed partial class InteractionsTests { - [Fact] - public async Task EventSubscribe_WhenSetupCallbackThrows_ShouldStillRecordSubscription() + public sealed class ThrowingCallbackTests { - ThrowingCallbackEventService sut = ThrowingCallbackEventService.CreateMock(); - sut.Mock.Setup.SomeEvent.OnSubscribed - .Do(() => throw new InvalidOperationException("callback throws")); + [Fact] + public async Task EventSubscribe_WhenSetupCallbackThrows_ShouldStillRecordSubscription() + { + ThrowingCallbackEventService sut = ThrowingCallbackEventService.CreateMock(); + sut.Mock.Setup.SomeEvent.OnSubscribed + .Do(() => throw new InvalidOperationException("callback throws")); - void Act() => sut.SomeEvent += Handler; + void Act() => sut.SomeEvent += Handler; - await That(Act).Throws(); - await That(sut.Mock.Verify.SomeEvent.Subscribed()).Once(); + await That(Act).Throws(); + await That(sut.Mock.Verify.SomeEvent.Subscribed()).Once(); - static void Handler(int value) - { + static void Handler(int value) + { + } } - } - [Fact] - public async Task EventUnsubscribe_WhenSetupCallbackThrows_ShouldStillRecordUnsubscription() - { - ThrowingCallbackEventService sut = ThrowingCallbackEventService.CreateMock(); - sut.Mock.Setup.SomeEvent.OnUnsubscribed - .Do(() => throw new InvalidOperationException("callback throws")); + [Fact] + public async Task EventUnsubscribe_WhenSetupCallbackThrows_ShouldStillRecordUnsubscription() + { + ThrowingCallbackEventService sut = ThrowingCallbackEventService.CreateMock(); + sut.Mock.Setup.SomeEvent.OnUnsubscribed + .Do(() => throw new InvalidOperationException("callback throws")); - void Act() => sut.SomeEvent -= Handler; + void Act() => sut.SomeEvent -= Handler; - await That(Act).Throws(); - await That(sut.Mock.Verify.SomeEvent.Unsubscribed()).Once(); + await That(Act).Throws(); + await That(sut.Mock.Verify.SomeEvent.Unsubscribed()).Once(); - static void Handler(int value) - { + static void Handler(int value) + { + } } - } - - public delegate void ThrowingCallbackEventHandler(int value); - public class ThrowingCallbackEventService - { - #pragma warning disable CA1070 - public virtual event ThrowingCallbackEventHandler? SomeEvent; - #pragma warning restore CA1070 + public delegate void ThrowingCallbackEventHandler(int value); - public void Raise(int value) => SomeEvent?.Invoke(value); + public class ThrowingCallbackEventService + { +#pragma warning disable CA1070 + public virtual event ThrowingCallbackEventHandler? SomeEvent + { + add => throw new InvalidOperationException("base add throws"); + remove => throw new InvalidOperationException("base remove throws"); + } +#pragma warning restore CA1070 + } } } diff --git a/Tests/Mockolate.Tests/MockEvents/InteractionsTests.cs b/Tests/Mockolate.Tests/MockEvents/InteractionsTests.cs index 141a3413..5b0246e9 100644 --- a/Tests/Mockolate.Tests/MockEvents/InteractionsTests.cs +++ b/Tests/Mockolate.Tests/MockEvents/InteractionsTests.cs @@ -4,7 +4,7 @@ namespace Mockolate.Tests.MockEvents; -public sealed class InteractionsTests +public sealed partial class InteractionsTests { [Fact] public async Task EventSubscription_ToString_ShouldReturnExpectedValue() diff --git a/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs b/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs index 3b9e1219..075a8b17 100644 --- a/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs +++ b/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs @@ -7,54 +7,314 @@ public sealed partial class InteractionsTests public sealed class ThrowingBaseTests { [Fact] - public async Task VirtualIndexerGetter_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + public async Task IndexerGetterWith1Parameter_WhenBaseThrows_ShouldStillRecordAccess() { - ThrowingBaseIndexer sut = ThrowingBaseIndexer.CreateMock(); - sut.Mock.Setup[It.IsAny().Monitor(out IParameterMonitor values)].Returns("foo"); + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); - void Act() => _ = sut[3]; + void Act() => _ = sut[1]; await That(Act).Throws(); - await That(values.Values).HasSingle().Which.IsEqualTo(3); + await That(sut.Mock.Verify[1].Got()).Once(); } [Fact] - public async Task VirtualIndexerGetter_WhenBaseThrows_ShouldStillRecordAccess() + public async Task IndexerGetterWith1Parameter_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() { - ThrowingBaseIndexer sut = ThrowingBaseIndexer.CreateMock(); + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + sut.Mock.Setup[It.IsAny().Monitor(out IParameterMonitor v1)].Returns("foo"); - void Act() => _ = sut[3]; + void Act() => _ = sut[1]; await That(Act).Throws(); - await That(sut.Mock.Verify[It.Is(3)].Got()).Once(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); } [Fact] - public async Task VirtualIndexerSetter_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + public async Task IndexerSetterWith1Parameter_WhenBaseThrows_ShouldStillRecordAccess() { - ThrowingBaseIndexer sut = ThrowingBaseIndexer.CreateMock(); - sut.Mock.Setup[It.IsAny().Monitor(out IParameterMonitor values)].Returns("foo"); + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); - void Act() => sut[3] = "value"; + void Act() => sut[1] = "value"; await That(Act).Throws(); - await That(values.Values).HasSingle().Which.IsEqualTo(3); + await That(sut.Mock.Verify[1].Set("value")).Once(); } [Fact] - public async Task VirtualIndexerSetter_WhenBaseThrows_ShouldStillRecordAccess() + public async Task IndexerSetterWith1Parameter_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() { - ThrowingBaseIndexer sut = ThrowingBaseIndexer.CreateMock(); + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + sut.Mock.Setup[It.IsAny().Monitor(out IParameterMonitor v1)].Returns("foo"); - void Act() => sut[3] = "value"; + void Act() => sut[1] = "value"; await That(Act).Throws(); - await That(sut.Mock.Verify[It.Is(3)].Set(It.Is("value"))).Once(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); } - public class ThrowingBaseIndexer + [Fact] + public async Task IndexerGetterWith2Parameters_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + + void Act() => _ = sut[1, 2]; + + await That(Act).Throws(); + await That(sut.Mock.Verify[1, 2].Got()).Once(); + } + + [Fact] + public async Task IndexerGetterWith2Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + sut.Mock.Setup[ + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2)] + .Returns("foo"); + + void Act() => _ = sut[1, 2]; + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + } + + [Fact] + public async Task IndexerSetterWith2Parameters_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + + void Act() => sut[1, 2] = "value"; + + await That(Act).Throws(); + await That(sut.Mock.Verify[1, 2].Set("value")).Once(); + } + + [Fact] + public async Task IndexerSetterWith2Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + sut.Mock.Setup[ + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2)] + .Returns("foo"); + + void Act() => sut[1, 2] = "value"; + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + } + + [Fact] + public async Task IndexerGetterWith3Parameters_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + + void Act() => _ = sut[1, 2, 3]; + + await That(Act).Throws(); + await That(sut.Mock.Verify[1, 2, 3].Got()).Once(); + } + + [Fact] + public async Task IndexerGetterWith3Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + sut.Mock.Setup[ + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2), + It.IsAny().Monitor(out IParameterMonitor v3)] + .Returns("foo"); + + void Act() => _ = sut[1, 2, 3]; + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + await That(v3.Values).HasSingle().Which.IsEqualTo(3); + } + + [Fact] + public async Task IndexerSetterWith3Parameters_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + + void Act() => sut[1, 2, 3] = "value"; + + await That(Act).Throws(); + await That(sut.Mock.Verify[1, 2, 3].Set("value")).Once(); + } + + [Fact] + public async Task IndexerSetterWith3Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + sut.Mock.Setup[ + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2), + It.IsAny().Monitor(out IParameterMonitor v3)] + .Returns("foo"); + + void Act() => sut[1, 2, 3] = "value"; + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + await That(v3.Values).HasSingle().Which.IsEqualTo(3); + } + + [Fact] + public async Task IndexerGetterWith4Parameters_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + + void Act() => _ = sut[1, 2, 3, 4]; + + await That(Act).Throws(); + await That(sut.Mock.Verify[1, 2, 3, 4].Got()).Once(); + } + + [Fact] + public async Task IndexerGetterWith4Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() { - public virtual string this[int key] + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + sut.Mock.Setup[ + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2), + It.IsAny().Monitor(out IParameterMonitor v3), + It.IsAny().Monitor(out IParameterMonitor v4)] + .Returns("foo"); + + void Act() => _ = sut[1, 2, 3, 4]; + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + await That(v3.Values).HasSingle().Which.IsEqualTo(3); + await That(v4.Values).HasSingle().Which.IsEqualTo(4); + } + + [Fact] + public async Task IndexerSetterWith4Parameters_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + + void Act() => sut[1, 2, 3, 4] = "value"; + + await That(Act).Throws(); + await That(sut.Mock.Verify[1, 2, 3, 4].Set("value")).Once(); + } + + [Fact] + public async Task IndexerSetterWith4Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + sut.Mock.Setup[ + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2), + It.IsAny().Monitor(out IParameterMonitor v3), + It.IsAny().Monitor(out IParameterMonitor v4)] + .Returns("foo"); + + void Act() => sut[1, 2, 3, 4] = "value"; + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + await That(v3.Values).HasSingle().Which.IsEqualTo(3); + await That(v4.Values).HasSingle().Which.IsEqualTo(4); + } + + [Fact] + public async Task IndexerGetterWith5Parameters_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + + void Act() => _ = sut[1, 2, 3, 4, 5]; + + await That(Act).Throws(); + await That(sut.Mock.Verify[1, 2, 3, 4, 5].Got()).Once(); + } + + [Fact] + public async Task IndexerGetterWith5Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + sut.Mock.Setup[ + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2), + It.IsAny().Monitor(out IParameterMonitor v3), + It.IsAny().Monitor(out IParameterMonitor v4), + It.IsAny().Monitor(out IParameterMonitor v5)] + .Returns("foo"); + + void Act() => _ = sut[1, 2, 3, 4, 5]; + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + await That(v3.Values).HasSingle().Which.IsEqualTo(3); + await That(v4.Values).HasSingle().Which.IsEqualTo(4); + await That(v5.Values).HasSingle().Which.IsEqualTo(5); + } + + [Fact] + public async Task IndexerSetterWith5Parameters_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + + void Act() => sut[1, 2, 3, 4, 5] = "value"; + + await That(Act).Throws(); + await That(sut.Mock.Verify[1, 2, 3, 4, 5].Set("value")).Once(); + } + + [Fact] + public async Task IndexerSetterWith5Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + sut.Mock.Setup[ + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2), + It.IsAny().Monitor(out IParameterMonitor v3), + It.IsAny().Monitor(out IParameterMonitor v4), + It.IsAny().Monitor(out IParameterMonitor v5)] + .Returns("foo"); + + void Act() => sut[1, 2, 3, 4, 5] = "value"; + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + await That(v3.Values).HasSingle().Which.IsEqualTo(3); + await That(v4.Values).HasSingle().Which.IsEqualTo(4); + await That(v5.Values).HasSingle().Which.IsEqualTo(5); + } + + public class ThrowingBaseIndexerService + { + public virtual string this[int p1] + { + get => throw new InvalidOperationException("base getter throws"); + set => throw new InvalidOperationException("base setter throws"); + } + public virtual string this[int p1, int p2] + { + get => throw new InvalidOperationException("base getter throws"); + set => throw new InvalidOperationException("base setter throws"); + } + public virtual string this[int p1, int p2, int p3] + { + get => throw new InvalidOperationException("base getter throws"); + set => throw new InvalidOperationException("base setter throws"); + } + public virtual string this[int p1, int p2, int p3, int p4] + { + get => throw new InvalidOperationException("base getter throws"); + set => throw new InvalidOperationException("base setter throws"); + } + public virtual string this[int p1, int p2, int p3, int p4, int p5] { get => throw new InvalidOperationException("base getter throws"); set => throw new InvalidOperationException("base setter throws"); diff --git a/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs b/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs index dae873bf..4da1db5e 100644 --- a/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs +++ b/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs @@ -7,56 +7,352 @@ public sealed partial class InteractionsTests public sealed class ThrowingBaseTests { [Fact] - public async Task VirtualMethod_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + public async Task VoidMethodWith0Parameters_WhenBaseThrows_ShouldStillRecordInvocation() { ThrowingBaseService sut = ThrowingBaseService.CreateMock(); - sut.Mock.Setup.DoThing(It.IsAny().Monitor(out IParameterMonitor values)).Returns(1); - void Act() => sut.DoThing(42); + void Act() => sut.VoidMethodWith0Parameters(); await That(Act).Throws(); - await That(values.Values).HasSingle().Which.IsEqualTo(42); + await That(sut.Mock.Verify.VoidMethodWith0Parameters()).Once(); } [Fact] - public async Task VirtualMethod_WhenBaseThrows_ShouldStillRecordInvocation() + public async Task VoidMethodWith1Parameter_WhenBaseThrows_ShouldStillRecordInvocation() { ThrowingBaseService sut = ThrowingBaseService.CreateMock(); - void Act() => sut.DoThing(42); + void Act() => sut.VoidMethodWith1Parameter(1); await That(Act).Throws(); - await That(sut.Mock.Verify.DoThing(42)).Once(); + await That(sut.Mock.Verify.VoidMethodWith1Parameter(1)).Once(); } [Fact] - public async Task VirtualVoidMethod_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + public async Task VoidMethodWith1Parameter_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() { ThrowingBaseService sut = ThrowingBaseService.CreateMock(); - sut.Mock.Setup.DoVoidThing(It.IsAny().Monitor(out IParameterMonitor values)).DoesNotThrow(); + sut.Mock.Setup.VoidMethodWith1Parameter(It.IsAny().Monitor(out IParameterMonitor v1)) + .DoesNotThrow(); - void Act() => sut.DoVoidThing(42); + void Act() => sut.VoidMethodWith1Parameter(1); await That(Act).Throws(); - await That(values.Values).HasSingle().Which.IsEqualTo(42); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); } [Fact] - public async Task VirtualVoidMethod_WhenBaseThrows_ShouldStillRecordInvocation() + public async Task VoidMethodWith2Parameters_WhenBaseThrows_ShouldStillRecordInvocation() { ThrowingBaseService sut = ThrowingBaseService.CreateMock(); - void Act() => sut.DoVoidThing(1); + void Act() => sut.VoidMethodWith2Parameters(1, 2); await That(Act).Throws(); - await That(sut.Mock.Verify.DoVoidThing(1)).Once(); + await That(sut.Mock.Verify.VoidMethodWith2Parameters(1, 2)).Once(); + } + + [Fact] + public async Task VoidMethodWith2Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + sut.Mock.Setup.VoidMethodWith2Parameters( + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2)) + .DoesNotThrow(); + + void Act() => sut.VoidMethodWith2Parameters(1, 2); + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + } + + [Fact] + public async Task VoidMethodWith3Parameters_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.VoidMethodWith3Parameters(1, 2, 3); + + await That(Act).Throws(); + await That(sut.Mock.Verify.VoidMethodWith3Parameters(1, 2, 3)).Once(); + } + + [Fact] + public async Task VoidMethodWith3Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + sut.Mock.Setup.VoidMethodWith3Parameters( + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2), + It.IsAny().Monitor(out IParameterMonitor v3)) + .DoesNotThrow(); + + void Act() => sut.VoidMethodWith3Parameters(1, 2, 3); + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + await That(v3.Values).HasSingle().Which.IsEqualTo(3); + } + + [Fact] + public async Task VoidMethodWith4Parameters_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.VoidMethodWith4Parameters(1, 2, 3, 4); + + await That(Act).Throws(); + await That(sut.Mock.Verify.VoidMethodWith4Parameters(1, 2, 3, 4)).Once(); + } + + [Fact] + public async Task VoidMethodWith4Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + sut.Mock.Setup.VoidMethodWith4Parameters( + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2), + It.IsAny().Monitor(out IParameterMonitor v3), + It.IsAny().Monitor(out IParameterMonitor v4)) + .DoesNotThrow(); + + void Act() => sut.VoidMethodWith4Parameters(1, 2, 3, 4); + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + await That(v3.Values).HasSingle().Which.IsEqualTo(3); + await That(v4.Values).HasSingle().Which.IsEqualTo(4); + } + + [Fact] + public async Task VoidMethodWith5Parameters_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.VoidMethodWith5Parameters(1, 2, 3, 4, 5); + + await That(Act).Throws(); + await That(sut.Mock.Verify.VoidMethodWith5Parameters(1, 2, 3, 4, 5)).Once(); + } + + [Fact] + public async Task VoidMethodWith5Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + sut.Mock.Setup.VoidMethodWith5Parameters( + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2), + It.IsAny().Monitor(out IParameterMonitor v3), + It.IsAny().Monitor(out IParameterMonitor v4), + It.IsAny().Monitor(out IParameterMonitor v5)) + .DoesNotThrow(); + + void Act() => sut.VoidMethodWith5Parameters(1, 2, 3, 4, 5); + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + await That(v3.Values).HasSingle().Which.IsEqualTo(3); + await That(v4.Values).HasSingle().Which.IsEqualTo(4); + await That(v5.Values).HasSingle().Which.IsEqualTo(5); + } + + [Fact] + public async Task ReturnMethodWith0Parameters_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.ReturnMethodWith0Parameters(); + + await That(Act).Throws(); + await That(sut.Mock.Verify.ReturnMethodWith0Parameters()).Once(); + } + + [Fact] + public async Task ReturnMethodWith1Parameter_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.ReturnMethodWith1Parameter(1); + + await That(Act).Throws(); + await That(sut.Mock.Verify.ReturnMethodWith1Parameter(1)).Once(); + } + + [Fact] + public async Task ReturnMethodWith1Parameter_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + sut.Mock.Setup.ReturnMethodWith1Parameter(It.IsAny().Monitor(out IParameterMonitor v1)) + .Returns(0); + + void Act() => sut.ReturnMethodWith1Parameter(1); + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + } + + [Fact] + public async Task ReturnMethodWith2Parameters_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.ReturnMethodWith2Parameters(1, 2); + + await That(Act).Throws(); + await That(sut.Mock.Verify.ReturnMethodWith2Parameters(1, 2)).Once(); + } + + [Fact] + public async Task ReturnMethodWith2Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + sut.Mock.Setup.ReturnMethodWith2Parameters( + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2)) + .Returns(0); + + void Act() => sut.ReturnMethodWith2Parameters(1, 2); + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + } + + [Fact] + public async Task ReturnMethodWith3Parameters_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.ReturnMethodWith3Parameters(1, 2, 3); + + await That(Act).Throws(); + await That(sut.Mock.Verify.ReturnMethodWith3Parameters(1, 2, 3)).Once(); + } + + [Fact] + public async Task ReturnMethodWith3Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + sut.Mock.Setup.ReturnMethodWith3Parameters( + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2), + It.IsAny().Monitor(out IParameterMonitor v3)) + .Returns(0); + + void Act() => sut.ReturnMethodWith3Parameters(1, 2, 3); + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + await That(v3.Values).HasSingle().Which.IsEqualTo(3); + } + + [Fact] + public async Task ReturnMethodWith4Parameters_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.ReturnMethodWith4Parameters(1, 2, 3, 4); + + await That(Act).Throws(); + await That(sut.Mock.Verify.ReturnMethodWith4Parameters(1, 2, 3, 4)).Once(); + } + + [Fact] + public async Task ReturnMethodWith4Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + sut.Mock.Setup.ReturnMethodWith4Parameters( + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2), + It.IsAny().Monitor(out IParameterMonitor v3), + It.IsAny().Monitor(out IParameterMonitor v4)) + .Returns(0); + + void Act() => sut.ReturnMethodWith4Parameters(1, 2, 3, 4); + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + await That(v3.Values).HasSingle().Which.IsEqualTo(3); + await That(v4.Values).HasSingle().Which.IsEqualTo(4); + } + + [Fact] + public async Task ReturnMethodWith5Parameters_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.ReturnMethodWith5Parameters(1, 2, 3, 4, 5); + + await That(Act).Throws(); + await That(sut.Mock.Verify.ReturnMethodWith5Parameters(1, 2, 3, 4, 5)).Once(); + } + + [Fact] + public async Task ReturnMethodWith5Parameters_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + sut.Mock.Setup.ReturnMethodWith5Parameters( + It.IsAny().Monitor(out IParameterMonitor v1), + It.IsAny().Monitor(out IParameterMonitor v2), + It.IsAny().Monitor(out IParameterMonitor v3), + It.IsAny().Monitor(out IParameterMonitor v4), + It.IsAny().Monitor(out IParameterMonitor v5)) + .Returns(0); + + void Act() => sut.ReturnMethodWith5Parameters(1, 2, 3, 4, 5); + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + await That(v2.Values).HasSingle().Which.IsEqualTo(2); + await That(v3.Values).HasSingle().Which.IsEqualTo(3); + await That(v4.Values).HasSingle().Which.IsEqualTo(4); + await That(v5.Values).HasSingle().Which.IsEqualTo(5); } public class ThrowingBaseService { - public virtual int DoThing(int value) => throw new InvalidOperationException("base throws"); + public virtual void VoidMethodWith0Parameters() + => throw new InvalidOperationException("base throws"); + + public virtual void VoidMethodWith1Parameter(int p1) + => throw new InvalidOperationException("base throws"); + + public virtual void VoidMethodWith2Parameters(int p1, int p2) + => throw new InvalidOperationException("base throws"); + + public virtual void VoidMethodWith3Parameters(int p1, int p2, int p3) + => throw new InvalidOperationException("base throws"); + + public virtual void VoidMethodWith4Parameters(int p1, int p2, int p3, int p4) + => throw new InvalidOperationException("base throws"); + + public virtual void VoidMethodWith5Parameters(int p1, int p2, int p3, int p4, int p5) + => throw new InvalidOperationException("base throws"); + + public virtual int ReturnMethodWith0Parameters() + => throw new InvalidOperationException("base throws"); + + public virtual int ReturnMethodWith1Parameter(int p1) + => throw new InvalidOperationException("base throws"); + + public virtual int ReturnMethodWith2Parameters(int p1, int p2) + => throw new InvalidOperationException("base throws"); + + public virtual int ReturnMethodWith3Parameters(int p1, int p2, int p3) + => throw new InvalidOperationException("base throws"); + + public virtual int ReturnMethodWith4Parameters(int p1, int p2, int p3, int p4) + => throw new InvalidOperationException("base throws"); - public virtual void DoVoidThing(int value) => throw new InvalidOperationException("base throws"); + public virtual int ReturnMethodWith5Parameters(int p1, int p2, int p3, int p4, int p5) + => throw new InvalidOperationException("base throws"); } } } diff --git a/Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs b/Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs index 367be45c..c0676c47 100644 --- a/Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs +++ b/Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs @@ -26,6 +26,32 @@ public async Task VirtualPropertySetter_WhenBaseThrows_ShouldStillRecordAccess() await That(sut.Mock.Verify.Value.Set(It.Is(42))).Once(); } + [Fact] + public async Task VirtualPropertyGetter_WhenBaseThrows_ShouldStillExecuteOnGetCallback() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + int callCount = 0; + sut.Mock.Setup.Value.OnGet.Do(() => callCount++); + + void Act() => _ = sut.Value; + + await That(Act).Throws(); + await That(callCount).IsEqualTo(1); + } + + [Fact] + public async Task VirtualPropertySetter_WhenBaseThrows_ShouldStillExecuteOnSetCallback() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + int receivedValue = 0; + sut.Mock.Setup.Value.OnSet.Do(v => receivedValue = v); + + void Act() => sut.Value = 42; + + await That(Act).Throws(); + await That(receivedValue).IsEqualTo(42); + } + public class ThrowingBaseService { public virtual int Value