diff --git a/.gitignore b/.gitignore index b0d183069..c44c5d5e1 100644 --- a/.gitignore +++ b/.gitignore @@ -307,3 +307,6 @@ docs/_site/* # Ignore Ionide files (https://ionide.io/) .ionide + +# kdiff/merge files +*.orig diff --git a/src/NSubstitute/Callback.cs b/src/NSubstitute/Callback.cs index 2d7f4dae2..7cf4a1705 100644 --- a/src/NSubstitute/Callback.cs +++ b/src/NSubstitute/Callback.cs @@ -18,7 +18,7 @@ public class Callback /// /// /// - public static ConfiguredCallback First(Action doThis) + public static ConfiguredCallback First(Action doThis) { return new ConfiguredCallback().Then(doThis); } @@ -28,7 +28,7 @@ public static ConfiguredCallback First(Action doThis) /// /// /// - public static Callback Always(Action doThis) + public static Callback Always(Action doThis) { return new ConfiguredCallback().AndAlways(doThis); } @@ -38,7 +38,7 @@ public static Callback Always(Action doThis) /// /// /// - public static ConfiguredCallback FirstThrow(Func throwThis) where TException : Exception + public static ConfiguredCallback FirstThrow(Func throwThis) where TException : Exception { return new ConfiguredCallback().ThenThrow(throwThis); } @@ -59,7 +59,7 @@ public static ConfiguredCallback FirstThrow(TException exception) wh /// The type of the exception. /// The throw this. /// - public static Callback AlwaysThrow(Func throwThis) where TException : Exception + public static Callback AlwaysThrow(Func throwThis) where TException : Exception { return new ConfiguredCallback().AndAlways(ToCallback(throwThis)); } @@ -75,33 +75,33 @@ public static Callback AlwaysThrow(TException exception) where TExce return AlwaysThrow(_ => exception); } - protected static Action ToCallback(Func throwThis) + protected static Action ToCallback(Func throwThis) where TException : notnull, Exception { return ci => { if (throwThis != null) throw throwThis(ci); }; } internal Callback() { } - private readonly ConcurrentQueue> callbackQueue = new ConcurrentQueue>(); - private Action alwaysDo = x => { }; - private Action keepDoing = x => { }; + private readonly ConcurrentQueue> callbackQueue = new ConcurrentQueue>(); + private Action alwaysDo = x => { }; + private Action keepDoing = x => { }; - protected void AddCallback(Action doThis) + protected void AddCallback(Action doThis) { callbackQueue.Enqueue(doThis); } - protected void SetAlwaysDo(Action always) + protected void SetAlwaysDo(Action always) { alwaysDo = always ?? (_ => { }); } - protected void SetKeepDoing(Action keep) + protected void SetKeepDoing(Action keep) { keepDoing = keep ?? (_ => { }); } - public void Call(CallInfo callInfo) + public void Call(ICallInfo callInfo) { try { @@ -113,7 +113,7 @@ public void Call(CallInfo callInfo) } } - private void CallFromStack(CallInfo callInfo) + private void CallFromStack(ICallInfo callInfo) { if (callbackQueue.TryDequeue(out var callback)) { diff --git a/src/NSubstitute/Callbacks/ConfiguredCallback.cs b/src/NSubstitute/Callbacks/ConfiguredCallback.cs index 0c32ad881..871b2cf58 100644 --- a/src/NSubstitute/Callbacks/ConfiguredCallback.cs +++ b/src/NSubstitute/Callbacks/ConfiguredCallback.cs @@ -13,7 +13,7 @@ internal ConfiguredCallback() { } /// /// Perform this action once in chain of called callbacks. /// - public ConfiguredCallback Then(Action doThis) + public ConfiguredCallback Then(Action doThis) { AddCallback(doThis); return this; @@ -22,7 +22,7 @@ public ConfiguredCallback Then(Action doThis) /// /// Keep doing this action after the other callbacks have run. /// - public EndCallbackChain ThenKeepDoing(Action doThis) + public EndCallbackChain ThenKeepDoing(Action doThis) { SetKeepDoing(doThis); return this; @@ -31,7 +31,7 @@ public EndCallbackChain ThenKeepDoing(Action doThis) /// /// Keep throwing this exception after the other callbacks have run. /// - public EndCallbackChain ThenKeepThrowing(Func throwThis) where TException : Exception => + public EndCallbackChain ThenKeepThrowing(Func throwThis) where TException : Exception => ThenKeepDoing(ToCallback(throwThis)); /// @@ -45,7 +45,7 @@ public EndCallbackChain ThenKeepThrowing(TException throwThis) where /// /// The type of the exception /// Produce the exception to throw for a CallInfo - public ConfiguredCallback ThenThrow(Func throwThis) where TException : Exception + public ConfiguredCallback ThenThrow(Func throwThis) where TException : Exception { AddCallback(ToCallback(throwThis)); return this; @@ -68,7 +68,7 @@ internal EndCallbackChain() { } /// Perform the given action for every call. /// /// The action to perform for every call - public Callback AndAlways(Action doThis) + public Callback AndAlways(Action doThis) { SetAlwaysDo(doThis); return this; diff --git a/src/NSubstitute/Core/CallInfo.cs b/src/NSubstitute/Core/CallInfo.cs index 89aa1b8ea..faf4bc652 100644 --- a/src/NSubstitute/Core/CallInfo.cs +++ b/src/NSubstitute/Core/CallInfo.cs @@ -9,72 +9,64 @@ namespace NSubstitute.Core { - public class CallInfo + public class CallInfo : ICallInfo { private readonly Argument[] _callArguments; + private readonly Func> _baseResult; - public CallInfo(Argument[] callArguments) - { + public CallInfo(Argument[] callArguments, Func> baseResult) { _callArguments = callArguments; + _baseResult = baseResult; + } + + protected CallInfo(CallInfo info) : this(info._callArguments, info._baseResult) { } /// - /// Gets the nth argument to this call. + /// Call and returns the result from the base implementation of a substitute for a class. + /// Will throw an exception if no base implementation exists. /// - /// Index of argument - /// The value of the argument at the given index - public object this[int index] - { + /// Result from base implementation + /// Throws in no base implementation exists + protected object GetBaseResult() { + return _baseResult().ValueOr(() => throw new NoBaseImplementationException()); + } + + /// + public object this[int index] { get => _callArguments[index].Value; - set - { + set { var argument = _callArguments[index]; EnsureArgIsSettable(argument, index, value); argument.Value = value; } } - private void EnsureArgIsSettable(Argument argument, int index, object value) - { - if (!argument.IsByRef) - { + private void EnsureArgIsSettable(Argument argument, int index, object value) { + if (!argument.IsByRef) { throw new ArgumentIsNotOutOrRefException(index, argument.DeclaredType); } - if (value != null && !argument.CanSetValueWithInstanceOf(value.GetType())) - { + if (value != null && !argument.CanSetValueWithInstanceOf(value.GetType())) { throw new ArgumentSetWithIncompatibleValueException(index, argument.DeclaredType, value.GetType()); } } - /// - /// Get the arguments passed to this call. - /// - /// Array of all arguments passed to this call + /// public object[] Args() => _callArguments.Select(x => x.Value).ToArray(); - /// - /// Gets the types of all the arguments passed to this call. - /// - /// Array of types of all arguments passed to this call + /// public Type[] ArgTypes() => _callArguments.Select(x => x.DeclaredType).ToArray(); - /// - /// Gets the argument of type `T` passed to this call. This will throw if there are no arguments - /// of this type, or if there is more than one matching argument. - /// - /// The type of the argument to retrieve - /// The argument passed to the call, or throws if there is not exactly one argument of this type - public T Arg() - { + /// + public T Arg() { T arg; if (TryGetArg(x => x.IsDeclaredTypeEqualToOrByRefVersionOf(typeof(T)), out arg)) return arg; if (TryGetArg(x => x.IsValueAssignableTo(typeof(T)), out arg)) return arg; throw new ArgumentNotFoundException("Can not find an argument of type " + typeof(T).FullName + " to this call."); } - private bool TryGetArg(Func condition, [MaybeNullWhen(false)] out T value) - { + private bool TryGetArg(Func condition, [MaybeNullWhen(false)] out T value) { value = default; var matchingArgs = _callArguments.Where(condition); @@ -85,10 +77,8 @@ private bool TryGetArg(Func condition, [MaybeNullWhen(false)] return true; } - private void ThrowIfMoreThanOne(IEnumerable arguments) - { - if (arguments.Skip(1).Any()) - { + private void ThrowIfMoreThanOne(IEnumerable arguments) { + if (arguments.Skip(1).Any()) { throw new AmbiguousArgumentsException( "There is more than one argument of type " + typeof(T).FullName + " to this call.\n" + "The call signature is (" + DisplayTypes(ArgTypes()) + ")\n" + @@ -97,27 +87,15 @@ private void ThrowIfMoreThanOne(IEnumerable arguments) } } - /// - /// Gets the argument passed to this call at the specified zero-based position, converted to type `T`. - /// This will throw if there are no arguments, if the argument is out of range or if it - /// cannot be converted to the specified type. - /// - /// The type of the argument to retrieve - /// The zero-based position of the argument to retrieve - /// The argument passed to the call, or throws if there is not exactly one argument of this type - public T ArgAt(int position) - { - if (position >= _callArguments.Length) - { + /// + public T ArgAt(int position) { + if (position >= _callArguments.Length) { throw new ArgumentOutOfRangeException(nameof(position), $"There is no argument at position {position}"); } - try - { - return (T) _callArguments[position].Value!; - } - catch (InvalidCastException) - { + try { + return (T)_callArguments[position].Value!; + } catch (InvalidCastException) { throw new InvalidCastException( $"Couldn't convert parameter at position {position} to type {typeof(T).FullName}"); } @@ -125,5 +103,8 @@ public T ArgAt(int position) private static string DisplayTypes(IEnumerable types) => string.Join(", ", types.Select(x => x.Name).ToArray()); + + /// + public ICallInfo ForCallReturning() => new CallInfo(this); } } diff --git a/src/NSubstitute/Core/CallInfoFactory.cs b/src/NSubstitute/Core/CallInfoFactory.cs index fa7662d07..a02cc3fa3 100644 --- a/src/NSubstitute/Core/CallInfoFactory.cs +++ b/src/NSubstitute/Core/CallInfoFactory.cs @@ -5,7 +5,7 @@ public class CallInfoFactory : ICallInfoFactory public CallInfo Create(ICall call) { var arguments = GetArgumentsFromCall(call); - return new CallInfo(arguments); + return new CallInfo(arguments, () => call.TryCallBase()); } private static Argument[] GetArgumentsFromCall(ICall call) diff --git a/src/NSubstitute/Core/CallInfoWithReturns.cs b/src/NSubstitute/Core/CallInfoWithReturns.cs new file mode 100644 index 000000000..5eb12aefd --- /dev/null +++ b/src/NSubstitute/Core/CallInfoWithReturns.cs @@ -0,0 +1,16 @@ +namespace NSubstitute.Core +{ + /// + /// Information for a call that returns a value of type T. + /// + /// + public class CallInfo : CallInfo, ICallInfo + { + internal CallInfo(CallInfo info) : base(info) { + } + + public T BaseResult() { + return (T)GetBaseResult(); + } + } +} \ No newline at end of file diff --git a/src/NSubstitute/Core/IReturn.cs b/src/NSubstitute/Core/IReturn.cs index 0901f2820..817e25008 100644 --- a/src/NSubstitute/Core/IReturn.cs +++ b/src/NSubstitute/Core/IReturn.cs @@ -9,13 +9,13 @@ namespace NSubstitute.Core { public interface IReturn { - object? ReturnFor(CallInfo info); + object? ReturnFor(ICallInfo info); Type? TypeOrNull(); bool CanBeAssignedTo(Type t); } /// - /// Performance optimization. Allows to not construct if configured result doesn't depend on it. + /// Performance optimization. Allows to not construct if configured result doesn't depend on it. /// internal interface ICallIndependentReturn { @@ -32,28 +32,28 @@ public ReturnValue(object? value) } public object? GetReturnValue() => _value; - public object? ReturnFor(CallInfo info) => GetReturnValue(); + public object? ReturnFor(ICallInfo info) => GetReturnValue(); public Type? TypeOrNull() => _value?.GetType(); public bool CanBeAssignedTo(Type t) => _value.IsCompatibleWith(t); } public class ReturnValueFromFunc : IReturn { - private readonly Func _funcToReturnValue; + private readonly Func, T?> _funcToReturnValue; - public ReturnValueFromFunc(Func? funcToReturnValue) + public ReturnValueFromFunc(Func, T?>? funcToReturnValue) { _funcToReturnValue = funcToReturnValue ?? ReturnNull(); } - public object? ReturnFor(CallInfo info) => _funcToReturnValue(info); - public Type TypeOrNull() => typeof (T); - public bool CanBeAssignedTo(Type t) => typeof (T).IsAssignableFrom(t); + public object? ReturnFor(ICallInfo info) => _funcToReturnValue(info.ForCallReturning()); + public Type TypeOrNull() => typeof(T); + public bool CanBeAssignedTo(Type t) => typeof(T).IsAssignableFrom(t); - private static Func ReturnNull() + private static Func ReturnNull() { if (typeof(T).GetTypeInfo().IsValueType) throw new CannotReturnNullForValueType(typeof(T)); - return x => default(T); + return x => default; } } @@ -69,28 +69,31 @@ public ReturnMultipleValues(T?[] values) } public object? GetReturnValue() => GetNext(); - public object? ReturnFor(CallInfo info) => GetReturnValue(); - public Type TypeOrNull() => typeof (T); - public bool CanBeAssignedTo(Type t) => typeof (T).IsAssignableFrom(t); + public object? ReturnFor(ICallInfo info) => GetReturnValue(); + public Type TypeOrNull() => typeof(T); + public bool CanBeAssignedTo(Type t) => typeof(T).IsAssignableFrom(t); private T? GetNext() => _valuesToReturn.TryDequeue(out var nextResult) ? nextResult : _lastValue; } public class ReturnMultipleFuncsValues : IReturn { - private readonly ConcurrentQueue> _funcsToReturn; - private readonly Func _lastFunc; + private readonly ConcurrentQueue, T?>> _funcsToReturn; + private readonly Func, T?> _lastFunc; - public ReturnMultipleFuncsValues(Func[] funcs) + public ReturnMultipleFuncsValues(Func, T?>[] funcs) { - _funcsToReturn = new ConcurrentQueue>(funcs); + _funcsToReturn = new ConcurrentQueue, T?>>(funcs); _lastFunc = funcs.Last(); } - public object? ReturnFor(CallInfo info) => GetNext(info); - public Type TypeOrNull() => typeof (T); - public bool CanBeAssignedTo(Type t) => typeof (T).IsAssignableFrom(t); + public object? ReturnFor(ICallInfo info) => GetNext(info); + public Type TypeOrNull() => typeof(T); + public bool CanBeAssignedTo(Type t) => typeof(T).IsAssignableFrom(t); - private T? GetNext(CallInfo info) => _funcsToReturn.TryDequeue(out var nextFunc) ? nextFunc(info) : _lastFunc(info); + private T? GetNext(ICallInfo info) => + _funcsToReturn.TryDequeue(out var nextFunc) + ? nextFunc(info.ForCallReturning()) + : _lastFunc(info.ForCallReturning()); } } \ No newline at end of file diff --git a/src/NSubstitute/Core/WhenCalled.cs b/src/NSubstitute/Core/WhenCalled.cs index fbc80c24d..daf9e4104 100644 --- a/src/NSubstitute/Core/WhenCalled.cs +++ b/src/NSubstitute/Core/WhenCalled.cs @@ -29,7 +29,7 @@ public WhenCalled(ISubstitutionContext context, T substitute, Action call, Ma /// Perform this action when called. /// /// - public void Do(Action callbackWithArguments) + public void Do(Action callbackWithArguments) { _threadContext.SetNextRoute(_callRouter, x => _routeFactory.DoWhenCalled(x, callbackWithArguments, _matchArgs)); _call(_substitute); @@ -82,7 +82,7 @@ public void Throw(Exception exception) => /// /// Throw an exception generated by the specified function when called. /// - public void Throw(Func createException) => + public void Throw(Func createException) => Do(ci => throw createException(ci)); } } \ No newline at end of file diff --git a/src/NSubstitute/Exceptions/NoBaseImplementationException.cs b/src/NSubstitute/Exceptions/NoBaseImplementationException.cs new file mode 100644 index 000000000..3ab623ff1 --- /dev/null +++ b/src/NSubstitute/Exceptions/NoBaseImplementationException.cs @@ -0,0 +1,11 @@ +namespace NSubstitute.Exceptions +{ + public class NoBaseImplementationException : SubstituteException + { + private const string Explanation = + "Cannot call the base method as the base method implementation is missing. " + + "You can call base method only if you create a class substitute and the method is not abstract."; + + public NoBaseImplementationException() : base(Explanation) { } + } +} diff --git a/src/NSubstitute/Extensions/ExceptionExtensions.cs b/src/NSubstitute/Extensions/ExceptionExtensions.cs index 60f3b5339..15004dc88 100644 --- a/src/NSubstitute/Extensions/ExceptionExtensions.cs +++ b/src/NSubstitute/Extensions/ExceptionExtensions.cs @@ -35,7 +35,7 @@ public static ConfiguredCall Throws(this object value) /// /// Func creating exception object /// - public static ConfiguredCall Throws(this object value, Func createException) => + public static ConfiguredCall Throws(this object value, Func createException) => value.Returns(ci => throw createException(ci)); /// @@ -65,7 +65,7 @@ public static ConfiguredCall ThrowsForAnyArgs(this object value) /// /// Func creating exception object /// - public static ConfiguredCall ThrowsForAnyArgs(this object value, Func createException) => + public static ConfiguredCall ThrowsForAnyArgs(this object value, Func createException) => value.ReturnsForAnyArgs(ci => throw createException(ci)); } } diff --git a/src/NSubstitute/Extensions/ReturnsForAllExtensions.cs b/src/NSubstitute/Extensions/ReturnsForAllExtensions.cs index 302033d4c..81df863c6 100644 --- a/src/NSubstitute/Extensions/ReturnsForAllExtensions.cs +++ b/src/NSubstitute/Extensions/ReturnsForAllExtensions.cs @@ -31,7 +31,7 @@ public static void ReturnsForAll(this object substitute, T returnThis) /// /// /// - public static void ReturnsForAll(this object substitute, Func returnThis) + public static void ReturnsForAll(this object substitute, Func returnThis) { if (substitute == null) throw new NullSubstituteReferenceException(); diff --git a/src/NSubstitute/ICallInfo.cs b/src/NSubstitute/ICallInfo.cs new file mode 100644 index 000000000..2a72b572f --- /dev/null +++ b/src/NSubstitute/ICallInfo.cs @@ -0,0 +1,68 @@ +using System; + +namespace NSubstitute +{ + /// + /// Access information on arguments for call. + /// + public interface ICallInfo + { + /// + /// Gets the nth argument to this call. + /// + /// Index of argument + /// The value of the argument at the given index + object this[int index] { get; set; } + + /// + /// Gets the argument of type `T` passed to this call. This will throw if there are no arguments + /// of this type, or if there is more than one matching argument. + /// + /// The type of the argument to retrieve + /// The argument passed to the call, or throws if there is not exactly one argument of this type + T Arg(); + + /// + /// Gets the argument passed to this call at the specified zero-based position, converted to type `T`. + /// This will throw if there are no arguments, if the argument is out of range or if it + /// cannot be converted to the specified type. + /// + /// The type of the argument to retrieve + /// The zero-based position of the argument to retrieve + /// The argument passed to the call, or throws if there is not exactly one argument of this type + T ArgAt(int position); + + /// + /// Get the arguments passed to this call. + /// + /// Array of all arguments passed to this call + object[] Args(); + + /// + /// Gets the types of all the arguments passed to this call. + /// + /// Array of types of all arguments passed to this call + Type[] ArgTypes(); + + /// + /// If we are sure this call returns a value of type , return an + /// that allows us to access the . + /// + /// This will not be checked by the compiler, so if this method is misused the resulting + /// may throw . + /// + /// + /// + ICallInfo ForCallReturning(); + } + + public interface ICallInfo : ICallInfo + { + /// + /// Calls the base implementation and attempts to cast the result to . + /// + /// Result from base (non-substituted) implementation of call + /// + T BaseResult(); + } +} \ No newline at end of file diff --git a/src/NSubstitute/Routing/Handlers/ReturnFromAndConfigureDynamicCall.cs b/src/NSubstitute/Routing/Handlers/ReturnFromAndConfigureDynamicCall.cs index e4fadd2c4..70a720dad 100644 --- a/src/NSubstitute/Routing/Handlers/ReturnFromAndConfigureDynamicCall.cs +++ b/src/NSubstitute/Routing/Handlers/ReturnFromAndConfigureDynamicCall.cs @@ -55,7 +55,7 @@ public ConfiguredCall Returns(T? returnThis, params T?[] returnThese) return default(T).Returns(returnThis, returnThese); } - public ConfiguredCall Returns(Func returnThis, params Func[] returnThese) + public ConfiguredCall Returns(Func returnThis, params Func[] returnThese) { return default(T).Returns(returnThis, returnThese); } @@ -65,7 +65,7 @@ public ConfiguredCall ReturnsForAnyArgs(T? returnThis, params T?[] returnThes return default(T).ReturnsForAnyArgs(returnThis, returnThese); } - public ConfiguredCall ReturnsForAnyArgs(Func returnThis, params Func[] returnThese) + public ConfiguredCall ReturnsForAnyArgs(Func returnThis, params Func[] returnThese) { return default(T).ReturnsForAnyArgs(returnThis, returnThese); } diff --git a/src/NSubstitute/SubstituteExtensions.Returns.Task.cs b/src/NSubstitute/SubstituteExtensions.Returns.Task.cs index 57388dfeb..6d8ba1cda 100644 --- a/src/NSubstitute/SubstituteExtensions.Returns.Task.cs +++ b/src/NSubstitute/SubstituteExtensions.Returns.Task.cs @@ -33,14 +33,14 @@ public static ConfiguredCall Returns(this Task value, T returnThis, params /// /// Function to calculate the return value /// Optionally use these functions next - public static ConfiguredCall Returns(this Task value, Func returnThis, params Func[] returnThese) + public static ConfiguredCall Returns(this Task value, Func returnThis, params Func[] returnThese) { ReThrowOnNSubstituteFault(value); var wrappedFunc = WrapFuncInTask(returnThis); var wrappedReturnThese = returnThese.Length > 0 ? returnThese.Select(WrapFuncInTask).ToArray() : null; - return ConfigureReturn(MatchArgs.AsSpecifiedInCall, wrappedFunc, wrappedReturnThese); + return ConfigureFuncReturn(MatchArgs.AsSpecifiedInCall, wrappedFunc, wrappedReturnThese); } /// @@ -65,14 +65,14 @@ public static ConfiguredCall ReturnsForAnyArgs(this Task value, T returnTh /// /// Function to calculate the return value /// Optionally use these functions next - public static ConfiguredCall ReturnsForAnyArgs(this Task value, Func returnThis, params Func[] returnThese) + public static ConfiguredCall ReturnsForAnyArgs(this Task value, Func returnThis, params Func[] returnThese) { ReThrowOnNSubstituteFault(value); var wrappedFunc = WrapFuncInTask(returnThis); var wrappedReturnThese = returnThese.Length > 0 ? returnThese.Select(WrapFuncInTask).ToArray() : null; - return ConfigureReturn(MatchArgs.Any, wrappedFunc, wrappedReturnThese); + return ConfigureFuncReturn(MatchArgs.Any, wrappedFunc, wrappedReturnThese); } #nullable restore @@ -86,7 +86,7 @@ private static void ReThrowOnNSubstituteFault(Task task) private static Task CompletedTask(T? result) => Task.FromResult(result); - private static Func> WrapFuncInTask(Func returnThis) => + private static Func> WrapFuncInTask(Func returnThis) => x => CompletedTask(returnThis(x)); } } \ No newline at end of file diff --git a/src/NSubstitute/SubstituteExtensions.Returns.ValueTask.cs b/src/NSubstitute/SubstituteExtensions.Returns.ValueTask.cs index d899f0f7d..91483835e 100644 --- a/src/NSubstitute/SubstituteExtensions.Returns.ValueTask.cs +++ b/src/NSubstitute/SubstituteExtensions.Returns.ValueTask.cs @@ -33,14 +33,14 @@ public static ConfiguredCall Returns(this ValueTask value, T returnThis, p /// /// Function to calculate the return value /// Optionally use these functions next - public static ConfiguredCall Returns(this ValueTask value, Func returnThis, params Func[] returnThese) + public static ConfiguredCall Returns(this ValueTask value, Func returnThis, params Func[] returnThese) { ReThrowOnNSubstituteFault(value); var wrappedFunc = WrapFuncInValueTask(returnThis); var wrappedReturnThese = returnThese.Length > 0 ? returnThese.Select(WrapFuncInValueTask).ToArray() : null; - return ConfigureReturn(MatchArgs.AsSpecifiedInCall, wrappedFunc, wrappedReturnThese); + return ConfigureFuncReturn(MatchArgs.AsSpecifiedInCall, wrappedFunc, wrappedReturnThese); } /// @@ -65,14 +65,14 @@ public static ConfiguredCall ReturnsForAnyArgs(this ValueTask value, T ret /// /// Function to calculate the return value /// Optionally use these functions next - public static ConfiguredCall ReturnsForAnyArgs(this ValueTask value, Func returnThis, params Func[] returnThese) + public static ConfiguredCall ReturnsForAnyArgs(this ValueTask value, Func returnThis, params Func[] returnThese) { ReThrowOnNSubstituteFault(value); var wrappedFunc = WrapFuncInValueTask(returnThis); var wrappedReturnThese = returnThese.Length > 0 ? returnThese.Select(WrapFuncInValueTask).ToArray() : null; - return ConfigureReturn(MatchArgs.Any, wrappedFunc, wrappedReturnThese); + return ConfigureFuncReturn(MatchArgs.Any, wrappedFunc, wrappedReturnThese); } #nullable restore @@ -86,7 +86,7 @@ private static void ReThrowOnNSubstituteFault(ValueTask task) private static ValueTask CompletedValueTask(T? result) => new(result); - private static Func> WrapFuncInValueTask(Func returnThis) => + private static Func> WrapFuncInValueTask(Func returnThis) => x => CompletedValueTask(returnThis(x)); } } \ No newline at end of file diff --git a/src/NSubstitute/SubstituteExtensions.Returns.cs b/src/NSubstitute/SubstituteExtensions.Returns.cs index 0e959ebd4..13bcbd57f 100644 --- a/src/NSubstitute/SubstituteExtensions.Returns.cs +++ b/src/NSubstitute/SubstituteExtensions.Returns.cs @@ -24,8 +24,8 @@ public static ConfiguredCall Returns(this T value, T returnThis, params T[] r /// /// Function to calculate the return value /// Optionally use these functions next - public static ConfiguredCall Returns(this T value, Func returnThis, params Func[] returnThese) => - ConfigureReturn(MatchArgs.AsSpecifiedInCall, returnThis, returnThese); + public static ConfiguredCall Returns(this T value, Func, T> returnThis, params Func, T>[] returnThese) => + ConfigureFuncReturn(MatchArgs.AsSpecifiedInCall, returnThis, returnThese); /// /// Set a return value for this call made with any arguments. @@ -43,8 +43,8 @@ public static ConfiguredCall ReturnsForAnyArgs(this T value, T returnThis, pa /// Function to calculate the return value /// Optionally use these functions next /// - public static ConfiguredCall ReturnsForAnyArgs(this T value, Func returnThis, params Func[] returnThese) => - ConfigureReturn(MatchArgs.Any, returnThis, returnThese); + public static ConfiguredCall ReturnsForAnyArgs(this T value, Func, T> returnThis, params Func, T>[] returnThese) => + ConfigureFuncReturn(MatchArgs.Any, returnThis, returnThese); #nullable restore private static ConfiguredCall ConfigureReturn(MatchArgs matchArgs, T? returnThis, T?[]? returnThese) @@ -64,7 +64,7 @@ private static ConfiguredCall ConfigureReturn(MatchArgs matchArgs, T? returnT .LastCallShouldReturn(returnValue, matchArgs); } - private static ConfiguredCall ConfigureReturn(MatchArgs matchArgs, Func returnThis, Func[]? returnThese) + private static ConfiguredCall ConfigureFuncReturn(MatchArgs matchArgs, Func, T?> returnThis, Func, T?>[]? returnThese) { IReturn returnValue; if (returnThese == null || returnThese.Length == 0) diff --git a/src/NSubstitute/SubstituteExtensions.When.Task.cs b/src/NSubstitute/SubstituteExtensions.When.Task.cs index 79a20a2fa..37231c93d 100644 --- a/src/NSubstitute/SubstituteExtensions.When.Task.cs +++ b/src/NSubstitute/SubstituteExtensions.When.Task.cs @@ -11,7 +11,7 @@ public static partial class SubstituteExtensions { /// /// Perform an action when this member is called. - /// Must be followed by to provide the callback. + /// Must be followed by to provide the callback. /// public static WhenCalled When(this T substitute, Func substituteCall) where T : class { @@ -20,7 +20,7 @@ public static WhenCalled When(this T substitute, Func substituteC /// /// Perform an action when this member is called with any arguments. - /// Must be followed by to provide the callback. + /// Must be followed by to provide the callback. /// public static WhenCalled WhenForAnyArgs(this T substitute, Func substituteCall) where T : class { diff --git a/src/NSubstitute/SubstituteExtensions.When.ValueTask.cs b/src/NSubstitute/SubstituteExtensions.When.ValueTask.cs index 7759804da..f0d1ece1c 100644 --- a/src/NSubstitute/SubstituteExtensions.When.ValueTask.cs +++ b/src/NSubstitute/SubstituteExtensions.When.ValueTask.cs @@ -11,7 +11,7 @@ public static partial class SubstituteExtensions { /// /// Perform an action when this member is called. - /// Must be followed by to provide the callback. + /// Must be followed by to provide the callback. /// public static WhenCalled When(this TSubstitute substitute, Func> substituteCall) where TSubstitute : class @@ -21,7 +21,7 @@ public static WhenCalled When(this TSubstitut /// /// Perform an action when this member is called with any arguments. - /// Must be followed by to provide the callback. + /// Must be followed by to provide the callback. /// public static WhenCalled WhenForAnyArgs(this TSubstitute substitute, Func> substituteCall) where TSubstitute : class diff --git a/src/NSubstitute/SubstituteExtensions.When.cs b/src/NSubstitute/SubstituteExtensions.When.cs index 055e2aa69..0da4b33c6 100644 --- a/src/NSubstitute/SubstituteExtensions.When.cs +++ b/src/NSubstitute/SubstituteExtensions.When.cs @@ -11,7 +11,7 @@ public static partial class SubstituteExtensions { /// /// Perform an action when this member is called. - /// Must be followed by to provide the callback. + /// Must be followed by to provide the callback. /// public static WhenCalled When(this T substitute, Action substituteCall) where T : class { @@ -20,7 +20,7 @@ public static WhenCalled When(this T substitute, Action substituteCall) /// /// Perform an action when this member is called with any arguments. - /// Must be followed by to provide the callback. + /// Must be followed by to provide the callback. /// public static WhenCalled WhenForAnyArgs(this T substitute, Action substituteCall) where T : class { diff --git a/tests/NSubstitute.Acceptance.Specs/CallbackCalling.cs b/tests/NSubstitute.Acceptance.Specs/CallbackCalling.cs index 95beadbee..e24dd47bb 100644 --- a/tests/NSubstitute.Acceptance.Specs/CallbackCalling.cs +++ b/tests/NSubstitute.Acceptance.Specs/CallbackCalling.cs @@ -100,7 +100,7 @@ public void Throw_exception_when_Throw_with_specific_exception() [Test] public void Throw_exception_when_Throw_with_exception_generator() { - Func createException = ci => new ArgumentException("Argument: " + ci.Args()[0]); + Func createException = ci => new ArgumentException("Argument: " + ci.Args()[0]); int called = 0; _something.When(x => x.Echo(Arg.Any())).Do(Callback.Always(x => called++)); _something.When(x => x.Echo(Arg.Any())).Do(Callback.AlwaysThrow(createException)); diff --git a/tests/NSubstitute.Acceptance.Specs/ReturnFromBase.cs b/tests/NSubstitute.Acceptance.Specs/ReturnFromBase.cs new file mode 100644 index 000000000..b17d73bac --- /dev/null +++ b/tests/NSubstitute.Acceptance.Specs/ReturnFromBase.cs @@ -0,0 +1,52 @@ +using System; +using NSubstitute.Exceptions; +using NUnit.Framework; + +namespace NSubstitute.Acceptance.Specs +{ + public class ReturnFromBase + { + public class Sample + { + public virtual string RepeatButLouder(string s) => s + "!"; + public virtual void VoidMethod() { } + } + + public abstract class SampleWithAbstractMethod + { + public abstract string NoBaseImplementation(); + } + + public interface ISample + { + string InterfaceMethod(); + } + + [Test] + public void UseBaseInReturn() { + var sub = Substitute.For(); + sub.RepeatButLouder(Arg.Any()).Returns(x => x.BaseResult() + "?"); + + Assert.AreEqual("Hi!?", sub.RepeatButLouder("Hi")); + } + + [Test] + public void CallWithNoBaseImplementation() { + var sub = Substitute.For(); + sub.NoBaseImplementation().Returns(x => x.BaseResult()); + + Assert.Throws(() => + sub.NoBaseImplementation() + ); + } + + [Test] + public void CallBaseForInterface() { + var sub = Substitute.For(); + sub.InterfaceMethod().Returns(x => x.BaseResult()); + Assert.Throws(() => + sub.InterfaceMethod() + ); + } + } +} \ No newline at end of file diff --git a/tests/NSubstitute.Acceptance.Specs/WhenCalledDo.cs b/tests/NSubstitute.Acceptance.Specs/WhenCalledDo.cs index 6778947c1..7029fb3d7 100644 --- a/tests/NSubstitute.Acceptance.Specs/WhenCalledDo.cs +++ b/tests/NSubstitute.Acceptance.Specs/WhenCalledDo.cs @@ -99,7 +99,7 @@ public void Throw_exception_when_Throw_with_specific_exception() [Test] public void Throw_exception_when_Throw_with_exception_generator() { - Func createException = ci => new ArgumentException("Argument: " + ci.Args()[0]); + Func createException = ci => new ArgumentException("Argument: " + ci.Args()[0]); int called = 0; _something.When(x => x.Echo(Arg.Any())).Do(x => called++); _something.When(x => x.Echo(Arg.Any())).Throw(createException);