Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add protected BaseResult() method to CallInfo. #641

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,6 @@ docs/_site/*

# Ignore Ionide files (https://ionide.io/)
.ionide

# kdiff/merge files
*.orig
26 changes: 13 additions & 13 deletions src/NSubstitute/Callback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class Callback
/// </summary>
/// <param name="doThis"></param>
/// <returns></returns>
public static ConfiguredCallback First(Action<CallInfo> doThis)
public static ConfiguredCallback First(Action<ICallInfo> doThis)
{
return new ConfiguredCallback().Then(doThis);
}
Expand All @@ -28,7 +28,7 @@ public static ConfiguredCallback First(Action<CallInfo> doThis)
/// </summary>
/// <param name="doThis"></param>
/// <returns></returns>
public static Callback Always(Action<CallInfo> doThis)
public static Callback Always(Action<ICallInfo> doThis)
{
return new ConfiguredCallback().AndAlways(doThis);
}
Expand All @@ -38,7 +38,7 @@ public static Callback Always(Action<CallInfo> doThis)
/// </summary>
/// <param name="throwThis"></param>
/// <returns></returns>
public static ConfiguredCallback FirstThrow<TException>(Func<CallInfo, TException> throwThis) where TException : Exception
public static ConfiguredCallback FirstThrow<TException>(Func<ICallInfo, TException> throwThis) where TException : Exception
{
return new ConfiguredCallback().ThenThrow(throwThis);
}
Expand All @@ -59,7 +59,7 @@ public static Callback Always(Action<CallInfo> doThis)
/// <typeparam name="TException">The type of the exception.</typeparam>
/// <param name="throwThis">The throw this.</param>
/// <returns></returns>
public static Callback AlwaysThrow<TException>(Func<CallInfo, TException> throwThis) where TException : Exception
public static Callback AlwaysThrow<TException>(Func<ICallInfo, TException> throwThis) where TException : Exception
{
return new ConfiguredCallback().AndAlways(ToCallback(throwThis));
}
Expand All @@ -75,33 +75,33 @@ public static Callback Always(Action<CallInfo> doThis)
return AlwaysThrow(_ => exception);
}

protected static Action<CallInfo> ToCallback<TException>(Func<CallInfo, TException> throwThis)
protected static Action<ICallInfo> ToCallback<TException>(Func<ICallInfo, TException> throwThis)
where TException : notnull, Exception
{
return ci => { if (throwThis != null) throw throwThis(ci); };
}

internal Callback() { }
private readonly ConcurrentQueue<Action<CallInfo>> callbackQueue = new ConcurrentQueue<Action<CallInfo>>();
private Action<CallInfo> alwaysDo = x => { };
private Action<CallInfo> keepDoing = x => { };
private readonly ConcurrentQueue<Action<ICallInfo>> callbackQueue = new ConcurrentQueue<Action<ICallInfo>>();
private Action<ICallInfo> alwaysDo = x => { };
private Action<ICallInfo> keepDoing = x => { };

protected void AddCallback(Action<CallInfo> doThis)
protected void AddCallback(Action<ICallInfo> doThis)
{
callbackQueue.Enqueue(doThis);
}

protected void SetAlwaysDo(Action<CallInfo> always)
protected void SetAlwaysDo(Action<ICallInfo> always)
{
alwaysDo = always ?? (_ => { });
}

protected void SetKeepDoing(Action<CallInfo> keep)
protected void SetKeepDoing(Action<ICallInfo> keep)
{
keepDoing = keep ?? (_ => { });
}

public void Call(CallInfo callInfo)
public void Call(ICallInfo callInfo)
{
try
{
Expand All @@ -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))
{
Expand Down
10 changes: 5 additions & 5 deletions src/NSubstitute/Callbacks/ConfiguredCallback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class ConfiguredCallback : EndCallbackChain
/// <summary>
/// Perform this action once in chain of called callbacks.
/// </summary>
public ConfiguredCallback Then(Action<CallInfo> doThis)
public ConfiguredCallback Then(Action<ICallInfo> doThis)
{
AddCallback(doThis);
return this;
Expand All @@ -22,7 +22,7 @@ public ConfiguredCallback Then(Action<CallInfo> doThis)
/// <summary>
/// Keep doing this action after the other callbacks have run.
/// </summary>
public EndCallbackChain ThenKeepDoing(Action<CallInfo> doThis)
public EndCallbackChain ThenKeepDoing(Action<ICallInfo> doThis)
{
SetKeepDoing(doThis);
return this;
Expand All @@ -31,7 +31,7 @@ public EndCallbackChain ThenKeepDoing(Action<CallInfo> doThis)
/// <summary>
/// Keep throwing this exception after the other callbacks have run.
/// </summary>
public EndCallbackChain ThenKeepThrowing<TException>(Func<CallInfo, TException> throwThis) where TException : Exception =>
public EndCallbackChain ThenKeepThrowing<TException>(Func<ICallInfo, TException> throwThis) where TException : Exception =>
ThenKeepDoing(ToCallback(throwThis));

/// <summary>
Expand All @@ -45,7 +45,7 @@ public EndCallbackChain ThenKeepDoing(Action<CallInfo> doThis)
/// </summary>
/// <typeparam name="TException">The type of the exception</typeparam>
/// <param name="throwThis">Produce the exception to throw for a CallInfo</param>
public ConfiguredCallback ThenThrow<TException>(Func<CallInfo, TException> throwThis) where TException : Exception
public ConfiguredCallback ThenThrow<TException>(Func<ICallInfo, TException> throwThis) where TException : Exception
{
AddCallback(ToCallback(throwThis));
return this;
Expand All @@ -68,7 +68,7 @@ public class EndCallbackChain : Callback
/// Perform the given action for every call.
/// </summary>
/// <param name="doThis">The action to perform for every call</param>
public Callback AndAlways(Action<CallInfo> doThis)
public Callback AndAlways(Action<ICallInfo> doThis)
{
SetAlwaysDo(doThis);
return this;
Expand Down
93 changes: 37 additions & 56 deletions src/NSubstitute/Core/CallInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,72 +9,64 @@

namespace NSubstitute.Core
{
public class CallInfo
public class CallInfo : ICallInfo
{
private readonly Argument[] _callArguments;
private readonly Func<Maybe<object>> _baseResult;

public CallInfo(Argument[] callArguments)
{
public CallInfo(Argument[] callArguments, Func<Maybe<object>> baseResult) {
_callArguments = callArguments;
_baseResult = baseResult;
}

protected CallInfo(CallInfo info) : this(info._callArguments, info._baseResult) {
}

/// <summary>
/// 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.
/// </summary>
/// <param name="index">Index of argument</param>
/// <returns>The value of the argument at the given index</returns>
public object this[int index]
{
/// <returns>Result from base implementation</returns>
/// <exception cref="NoBaseImplementationException">Throws in no base implementation exists</exception>
protected object GetBaseResult() {
return _baseResult().ValueOr(() => throw new NoBaseImplementationException());
}

/// <inheritdoc/>
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());
}
}

/// <summary>
/// Get the arguments passed to this call.
/// </summary>
/// <returns>Array of all arguments passed to this call</returns>
/// <inheritdoc/>
public object[] Args() => _callArguments.Select(x => x.Value).ToArray();

/// <summary>
/// Gets the types of all the arguments passed to this call.
/// </summary>
/// <returns>Array of types of all arguments passed to this call</returns>
/// <inheritdoc/>
public Type[] ArgTypes() => _callArguments.Select(x => x.DeclaredType).ToArray();

/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">The type of the argument to retrieve</typeparam>
/// <returns>The argument passed to the call, or throws if there is not exactly one argument of this type</returns>
public T Arg<T>()
{
/// <inheritdoc/>
public T Arg<T>() {
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<T>(Func<Argument, bool> condition, [MaybeNullWhen(false)] out T value)
{
private bool TryGetArg<T>(Func<Argument, bool> condition, [MaybeNullWhen(false)] out T value) {
value = default;

var matchingArgs = _callArguments.Where(condition);
Expand All @@ -85,10 +77,8 @@ private bool TryGetArg<T>(Func<Argument, bool> condition, [MaybeNullWhen(false)]
return true;
}

private void ThrowIfMoreThanOne<T>(IEnumerable<Argument> arguments)
{
if (arguments.Skip(1).Any())
{
private void ThrowIfMoreThanOne<T>(IEnumerable<Argument> 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" +
Expand All @@ -97,33 +87,24 @@ private void ThrowIfMoreThanOne<T>(IEnumerable<Argument> arguments)
}
}

/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">The type of the argument to retrieve</typeparam>
/// <param name="position">The zero-based position of the argument to retrieve</param>
/// <returns>The argument passed to the call, or throws if there is not exactly one argument of this type</returns>
public T ArgAt<T>(int position)
{
if (position >= _callArguments.Length)
{
/// <inheritdoc/>
public T ArgAt<T>(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}");
}
}

private static string DisplayTypes(IEnumerable<Type> types) =>
string.Join(", ", types.Select(x => x.Name).ToArray());

/// <inheritdoc/>
public ICallInfo<T> ForCallReturning<T>() => new CallInfo<T>(this);
}
}
2 changes: 1 addition & 1 deletion src/NSubstitute/Core/CallInfoFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 16 additions & 0 deletions src/NSubstitute/Core/CallInfoWithReturns.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace NSubstitute.Core
{
/// <summary>
/// Information for a call that returns a value of type <c>T</c>.
/// </summary>
/// <typeparam name="T"></typeparam>
public class CallInfo<T> : CallInfo, ICallInfo<T>
{
internal CallInfo(CallInfo info) : base(info) {
}

public T BaseResult() {
return (T)GetBaseResult();
}
}
}