Skip to content

Commit 05ed0a7

Browse files
authored
Merge pull request #855 from JMolenkamp/main
Feature: allow interception of any generic method call when using Arg.AnyType
2 parents fed12c4 + 25b1a5f commit 05ed0a7

File tree

3 files changed

+43
-3
lines changed

3 files changed

+43
-3
lines changed

src/NSubstitute/Core/CallInfo.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
using NSubstitute.Exceptions;
21
using System.Diagnostics.CodeAnalysis;
2+
using NSubstitute.Exceptions;
33

44
namespace NSubstitute.Core;
55

6-
public class CallInfo(Argument[] callArguments)
6+
public class CallInfo(Argument[] callArguments, Type[] genericArguments)
77
{
88

99
/// <summary>
@@ -35,6 +35,12 @@ private void EnsureArgIsSettable(Argument argument, int index, object? value)
3535
}
3636
}
3737

38+
/// <summary>
39+
/// Gets the generic type arguments in case of a generic method call or an empty array otherwise.
40+
/// </summary>
41+
/// <returns>Array of types of the generic type arguments for this method call</returns>
42+
public Type[] GenericArgs() => genericArguments;
43+
3844
/// <summary>
3945
/// Get the arguments passed to this call.
4046
/// </summary>

src/NSubstitute/Core/CallInfoFactory.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ public class CallInfoFactory : ICallInfoFactory
55
public CallInfo Create(ICall call)
66
{
77
var arguments = GetArgumentsFromCall(call);
8-
return new CallInfo(arguments);
8+
var genericArguments = call.GetMethodInfo().GetGenericArguments();
9+
return new CallInfo(arguments, genericArguments);
910
}
1011

1112
private static Argument[] GetArgumentsFromCall(ICall call)

tests/NSubstitute.Acceptance.Specs/GenericArguments.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections;
22
using System.Globalization;
3+
using System.Reflection;
34
using NUnit.Framework;
45

56
namespace NSubstitute.Acceptance.Specs;
@@ -12,6 +13,7 @@ public interface ISomethingWithGenerics
1213
void SomeAction<TState>(int level, TState state);
1314
string SomeFunction<TState>(int level, TState state);
1415
ICollection<TState> SomeFunction<TState>(TState state);
16+
ICollection<TState> SomeFunctionWithoutGenericMethodParameter<TState>(int level);
1517
bool SomeFunctionWithOut<TState>(out IEnumerable<TState> state);
1618
bool SomeFunctionWithRef<TState>(ref IEnumerable<TState> state);
1719
void SomeActionWithGenericConstraints<TState>(int level, TState state) where TState : IEnumerable<int>;
@@ -177,4 +179,35 @@ public void Returns_works_with_AnyType_for_ref_parameter_with_AnyType_generic_ar
177179

178180
Assert.That(result, Is.True);
179181
}
182+
183+
[Test]
184+
public void Callback_allows_access_to_method_call()
185+
{
186+
static ICollection<T> CreateSubstitute<T>(int count)
187+
{
188+
ICollection<T> substitute = Substitute.For<ICollection<T>>();
189+
substitute.Count.Returns(count);
190+
return substitute;
191+
}
192+
193+
MethodInfo methodInfo = typeof(GenericArguments)
194+
.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
195+
.Single(x
196+
=> x.Name.Contains(nameof(CreateSubstitute))
197+
&& x.Name.Contains(nameof(Callback_allows_access_to_method_call)));
198+
199+
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
200+
something
201+
.SomeFunctionWithoutGenericMethodParameter<Arg.AnyType>(Arg.Any<int>())
202+
.Returns(x =>
203+
{
204+
Type argumentType = x.GenericArgs()[0];
205+
MethodInfo method = methodInfo.MakeGenericMethod(argumentType);
206+
return method.Invoke(null, [x.Arg<int>()]);
207+
});
208+
209+
ICollection<int> result = something.SomeFunctionWithoutGenericMethodParameter<int>(7);
210+
211+
Assert.That(result.Count, Is.EqualTo(7));
212+
}
180213
}

0 commit comments

Comments
 (0)