From 25b1a5f3bdac6e858571fb4d05b690aad74f8ca0 Mon Sep 17 00:00:00 2001 From: Joost Molenkamp Date: Wed, 29 Jan 2025 21:12:49 +0100 Subject: [PATCH] Feature: allow access to MethodInfo from CallInfo --- src/NSubstitute/Core/CallInfo.cs | 10 ++++-- src/NSubstitute/Core/CallInfoFactory.cs | 3 +- .../GenericArguments.cs | 33 +++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/NSubstitute/Core/CallInfo.cs b/src/NSubstitute/Core/CallInfo.cs index d4c920ae..d21e4d2a 100644 --- a/src/NSubstitute/Core/CallInfo.cs +++ b/src/NSubstitute/Core/CallInfo.cs @@ -1,9 +1,9 @@ -using NSubstitute.Exceptions; using System.Diagnostics.CodeAnalysis; +using NSubstitute.Exceptions; namespace NSubstitute.Core; -public class CallInfo(Argument[] callArguments) +public class CallInfo(Argument[] callArguments, Type[] genericArguments) { /// @@ -35,6 +35,12 @@ private void EnsureArgIsSettable(Argument argument, int index, object? value) } } + /// + /// Gets the generic type arguments in case of a generic method call or an empty array otherwise. + /// + /// Array of types of the generic type arguments for this method call + public Type[] GenericArgs() => genericArguments; + /// /// Get the arguments passed to this call. /// diff --git a/src/NSubstitute/Core/CallInfoFactory.cs b/src/NSubstitute/Core/CallInfoFactory.cs index 983c652e..49506498 100644 --- a/src/NSubstitute/Core/CallInfoFactory.cs +++ b/src/NSubstitute/Core/CallInfoFactory.cs @@ -5,7 +5,8 @@ public class CallInfoFactory : ICallInfoFactory public CallInfo Create(ICall call) { var arguments = GetArgumentsFromCall(call); - return new CallInfo(arguments); + var genericArguments = call.GetMethodInfo().GetGenericArguments(); + return new CallInfo(arguments, genericArguments); } private static Argument[] GetArgumentsFromCall(ICall call) diff --git a/tests/NSubstitute.Acceptance.Specs/GenericArguments.cs b/tests/NSubstitute.Acceptance.Specs/GenericArguments.cs index 64b07f2d..aa173e13 100644 --- a/tests/NSubstitute.Acceptance.Specs/GenericArguments.cs +++ b/tests/NSubstitute.Acceptance.Specs/GenericArguments.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Globalization; +using System.Reflection; using NUnit.Framework; namespace NSubstitute.Acceptance.Specs; @@ -12,6 +13,7 @@ public interface ISomethingWithGenerics void SomeAction(int level, TState state); string SomeFunction(int level, TState state); ICollection SomeFunction(TState state); + ICollection SomeFunctionWithoutGenericMethodParameter(int level); bool SomeFunctionWithOut(out IEnumerable state); bool SomeFunctionWithRef(ref IEnumerable state); void SomeActionWithGenericConstraints(int level, TState state) where TState : IEnumerable; @@ -177,4 +179,35 @@ public void Returns_works_with_AnyType_for_ref_parameter_with_AnyType_generic_ar Assert.That(result, Is.True); } + + [Test] + public void Callback_allows_access_to_method_call() + { + static ICollection CreateSubstitute(int count) + { + ICollection substitute = Substitute.For>(); + substitute.Count.Returns(count); + return substitute; + } + + MethodInfo methodInfo = typeof(GenericArguments) + .GetMethods(BindingFlags.Static | BindingFlags.NonPublic) + .Single(x + => x.Name.Contains(nameof(CreateSubstitute)) + && x.Name.Contains(nameof(Callback_allows_access_to_method_call))); + + ISomethingWithGenerics something = Substitute.For(); + something + .SomeFunctionWithoutGenericMethodParameter(Arg.Any()) + .Returns(x => + { + Type argumentType = x.GenericArgs()[0]; + MethodInfo method = methodInfo.MakeGenericMethod(argumentType); + return method.Invoke(null, [x.Arg()]); + }); + + ICollection result = something.SomeFunctionWithoutGenericMethodParameter(7); + + Assert.That(result.Count, Is.EqualTo(7)); + } } \ No newline at end of file