diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs index 1d533229..e18239d3 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs @@ -1888,47 +1888,58 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb, .AppendLine(); } + if (hasOutParams) + { + foreach (MethodParameter parameter in method.Parameters.Where(p => p.RefKind == RefKind.Out)) + { + sb.Append("\t\t\t").Append(parameter.Name).Append(" = default!;").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(); + + sb.Append("\t\t\ttry").AppendLine(); + sb.Append("\t\t\t{").AppendLine(); + if (supportsWrapping) { - sb.Append("\t\t\tif (").Append(mockRegistry).Append(".Wraps is ").Append(className) + sb.Append("\t\t\t\tif (").Append(mockRegistry).Append(".Wraps is ").Append(className) .Append(" wraps)").AppendLine(); - sb.Append("\t\t\t{").AppendLine(); + sb.Append("\t\t\t\t{").AppendLine(); if (method.ReturnType != Type.Void) { - sb.Append("\t\t\t\t").Append(wrappedResult).Append(" = wraps").Append(".") + 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").Append(hasWrappedResult).Append(" = true;").AppendLine(); + sb.Append("\t\t\t\t\t").Append(hasWrappedResult).Append(" = true;").AppendLine(); } else { - sb.Append("\t\t\t\twraps").Append(".") + sb.Append("\t\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\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) - { - foreach (MethodParameter parameter in method.Parameters.Where(p => p.RefKind == RefKind.Out)) - { - sb.Append("\t\t\t").Append(parameter.Name).Append(" = default!;").AppendLine(); - } + sb.Append("\t\t\t\t}").AppendLine(); } if (!isAbstractOrInterface) @@ -1936,25 +1947,25 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb, 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) { @@ -1962,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(" = "); @@ -1972,79 +1983,67 @@ 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\tif (").Append(mockRegistry).Append(".Behavior.SkipInteractionRecording == false)").AppendLine(); + sb.Append("\t\t\t}").AppendLine(); + sb.Append("\t\t\tfinally").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(); + 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))})"; @@ -2053,8 +2052,6 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb, 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 3f97a1e8..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!; - } - if (!(methodSetup?.SkipBaseClass(this.MockRegistry.Behavior) ?? this.MockRegistry.Behavior.SkipBaseClass) && !hasWrappedResult) + flag = default!; + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { - base.MyMethod1(index, ref value1, out flag); - hasWrappedResult = true; + this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.MyService.MyMethod1", "index", index, "value1", value1, "flag", flag)); } - 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.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)!); - } } - if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + finally { - this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.MyService.MyMethod1", "index", index, "value1", value1, "flag", flag)); + 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(""" @@ -645,38 +684,44 @@ protected override bool MyMethod2(int index, bool isReadOnly, ref int value1, ou bool hasWrappedResult = false; bool wrappedResult = default!; flag = default!; - if (!(methodSetup?.SkipBaseClass(this.MockRegistry.Behavior) ?? this.MockRegistry.Behavior.SkipBaseClass)) + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { - wrappedResult = base.MyMethod2(index, isReadOnly, ref value1, out flag); - hasWrappedResult = true; + this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.MyService.MyMethod2", "index", index, "isReadOnly", isReadOnly, "value1", value1, "flag", flag)); } - 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)!); - } } - if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + finally { - this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.MyService.MyMethod2", "index", index, "isReadOnly", isReadOnly, "value1", value1, "flag", flag)); + 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) + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { - wraps.MyMethod1(ref index); - hasWrappedResult = true; + 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 + { + } } } - if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + finally { - this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod1", "index", index)); + 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!; + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) { - isReadOnly = default!; + 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)!); - } } - if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + finally { - this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.MethodInvocation("global::MyCode.IMyService.MyMethod2", "index", index, "isReadOnly", isReadOnly)); + 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 new file mode 100644 index 00000000..17368610 --- /dev/null +++ b/Tests/Mockolate.Tests/MockEvents/InteractionsTests.ThrowingCallbackTests.cs @@ -0,0 +1,54 @@ +namespace Mockolate.Tests.MockEvents; + +public sealed partial class InteractionsTests +{ + public sealed class ThrowingCallbackTests + { + [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 + { +#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 new file mode 100644 index 00000000..075a8b17 --- /dev/null +++ b/Tests/Mockolate.Tests/MockIndexers/InteractionsTests.ThrowingBaseTests.cs @@ -0,0 +1,324 @@ +using Mockolate.Parameters; + +namespace Mockolate.Tests.MockIndexers; + +public sealed partial class InteractionsTests +{ + public sealed class ThrowingBaseTests + { + [Fact] + public async Task IndexerGetterWith1Parameter_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + + void Act() => _ = sut[1]; + + await That(Act).Throws(); + await That(sut.Mock.Verify[1].Got()).Once(); + } + + [Fact] + public async Task IndexerGetterWith1Parameter_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + sut.Mock.Setup[It.IsAny().Monitor(out IParameterMonitor v1)].Returns("foo"); + + void Act() => _ = sut[1]; + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + } + + [Fact] + public async Task IndexerSetterWith1Parameter_WhenBaseThrows_ShouldStillRecordAccess() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + + void Act() => sut[1] = "value"; + + await That(Act).Throws(); + await That(sut.Mock.Verify[1].Set("value")).Once(); + } + + [Fact] + public async Task IndexerSetterWith1Parameter_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseIndexerService sut = ThrowingBaseIndexerService.CreateMock(); + sut.Mock.Setup[It.IsAny().Monitor(out IParameterMonitor v1)].Returns("foo"); + + void Act() => sut[1] = "value"; + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + } + + [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() + { + 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/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 new file mode 100644 index 00000000..4da1db5e --- /dev/null +++ b/Tests/Mockolate.Tests/MockMethods/InteractionsTests.ThrowingBaseTests.cs @@ -0,0 +1,358 @@ +using Mockolate.Parameters; + +namespace Mockolate.Tests.MockMethods; + +public sealed partial class InteractionsTests +{ + public sealed class ThrowingBaseTests + { + [Fact] + public async Task VoidMethodWith0Parameters_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.VoidMethodWith0Parameters(); + + await That(Act).Throws(); + await That(sut.Mock.Verify.VoidMethodWith0Parameters()).Once(); + } + + [Fact] + public async Task VoidMethodWith1Parameter_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.VoidMethodWith1Parameter(1); + + await That(Act).Throws(); + await That(sut.Mock.Verify.VoidMethodWith1Parameter(1)).Once(); + } + + [Fact] + public async Task VoidMethodWith1Parameter_WhenBaseThrows_ShouldRecordArgumentsPassedByCaller() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + sut.Mock.Setup.VoidMethodWith1Parameter(It.IsAny().Monitor(out IParameterMonitor v1)) + .DoesNotThrow(); + + void Act() => sut.VoidMethodWith1Parameter(1); + + await That(Act).Throws(); + await That(v1.Values).HasSingle().Which.IsEqualTo(1); + } + + [Fact] + public async Task VoidMethodWith2Parameters_WhenBaseThrows_ShouldStillRecordInvocation() + { + ThrowingBaseService sut = ThrowingBaseService.CreateMock(); + + void Act() => sut.VoidMethodWith2Parameters(1, 2); + + await That(Act).Throws(); + 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 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 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 new file mode 100644 index 00000000..c0676c47 --- /dev/null +++ b/Tests/Mockolate.Tests/MockProperties/InteractionsTests.ThrowingBaseTests.cs @@ -0,0 +1,64 @@ +namespace Mockolate.Tests.MockProperties; + +public sealed partial class InteractionsTests +{ + public sealed class ThrowingBaseTests + { + [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(); + } + + [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 + { + 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()