Skip to content

Commit 6db536b

Browse files
authored
Merge pull request #733 from icalvo/anytype
Implemented Arg.Is matcher and generic constraints
2 parents 41bba78 + c164f87 commit 6db536b

File tree

5 files changed

+142
-16
lines changed

5 files changed

+142
-16
lines changed

src/NSubstitute/Arg.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ namespace NSubstitute
1414
/// </summary>
1515
public static class Arg
1616
{
17-
public class AnyType
17+
/// <summary>
18+
/// This type can be used with any matcher to match a generic type parameter.
19+
/// </summary>
20+
/// <remarks>
21+
/// If the generic type parameter has constraints, you will have to create a derived class/struct that
22+
/// implements those constraints.
23+
/// </remarks>
24+
public interface AnyType
1825
{
1926
}
2027

@@ -43,6 +50,15 @@ public static ref T Is<T>(Expression<Predicate<T>> predicate)
4350
return ref ArgumentMatcher.Enqueue<T>(new ExpressionArgumentMatcher<T>(predicate));
4451
}
4552

53+
/// <summary>
54+
/// Match argument that satisfies <paramref name="predicate"/>.
55+
/// If the <paramref name="predicate"/> throws an exception for an argument it will be treated as non-matching.
56+
/// </summary>
57+
public static ref T Is<T>(Expression<Predicate<object>> predicate) where T : AnyType
58+
{
59+
return ref ArgumentMatcher.Enqueue<T>(new ExpressionArgumentMatcher<object>(predicate));
60+
}
61+
4662
/// <summary>
4763
/// Invoke any <see cref="Action"/> argument whenever a matching call is made to the substitute.
4864
/// </summary>
@@ -105,9 +121,9 @@ public static ref T Do<T>(Action<T> useArgument)
105121
/// Capture any argument compatible with type <typeparamref name="T"/> and use it to call the <paramref name="useArgument"/> function
106122
/// whenever a matching call is made to the substitute.
107123
/// </summary>
108-
public static ref AnyType Do<T>(Action<object> useArgument) where T : AnyType
124+
public static ref T Do<T>(Action<object> useArgument) where T : AnyType
109125
{
110-
return ref ArgumentMatcher.Enqueue<AnyType>(new AnyArgumentMatcher(typeof(AnyType)), x => useArgument(x!));
126+
return ref ArgumentMatcher.Enqueue<T>(new AnyArgumentMatcher(typeof(AnyType)), x => useArgument(x!));
111127
}
112128

113129
/// <summary>
@@ -141,6 +157,14 @@ public static class Compat
141157
/// </summary>
142158
public static T Is<T>(Expression<Predicate<T>> predicate) => Arg.Is(predicate);
143159

160+
/// <summary>
161+
/// Match argument that satisfies <paramref name="predicate"/>.
162+
/// If the <paramref name="predicate"/> throws an exception for an argument it will be treated as non-matching.
163+
/// This is provided for compatibility with older compilers --
164+
/// if possible use <see cref="Arg.Is{T}(Expression{Predicate{T}})" /> instead.
165+
/// </summary>
166+
public static AnyType Is<T>(Expression<Predicate<object>> predicate) where T : AnyType => Arg.Is<T>(predicate);
167+
144168
/// <summary>
145169
/// Invoke any <see cref="Action"/> argument whenever a matching call is made to the substitute.
146170
/// This is provided for compatibility with older compilers --

src/NSubstitute/Compatibility/CompatArg.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ private CompatArg() { }
5050
/// </summary>
5151
public T Is<T>(Expression<Predicate<T>> predicate) => Arg.Is(predicate);
5252

53+
/// <summary>
54+
/// Match argument that satisfies <paramref name="predicate"/>.
55+
/// If the <paramref name="predicate"/> throws an exception for an argument it will be treated as non-matching.
56+
/// This is provided for compatibility with older compilers --
57+
/// if possible use <see cref="Arg.Is{T}(Expression{Predicate{object}})" /> instead.
58+
/// </summary>
59+
public Arg.AnyType Is<T>(Expression<Predicate<object>> predicate) where T : Arg.AnyType => Arg.Is<T>(predicate);
60+
5361
/// <summary>
5462
/// Invoke any <see cref="Action"/> argument whenever a matching call is made to the substitute.
5563
/// This is provided for compatibility with older compilers --

src/NSubstitute/Core/CallSpecification.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ private static bool TypesAreAllEquivalent(Type[] aArgs, Type[] bArgs)
7878
var second = bArgs[i];
7979

8080
var areEquivalent = first.IsAssignableFrom(second) || second.IsAssignableFrom(first) ||
81-
first == typeof(Arg.AnyType) || second == typeof(Arg.AnyType);
81+
typeof(Arg.AnyType).IsAssignableFrom(first) || typeof(Arg.AnyType).IsAssignableFrom(second);
8282
if (!areEquivalent) return false;
8383
}
8484
return true;

src/NSubstitute/Core/Extensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal static class Extensions
1414
/// </summary>
1515
public static bool IsCompatibleWith(this object? instance, Type type)
1616
{
17-
if (type == typeof(Arg.AnyType))
17+
if (typeof(Arg.AnyType).IsAssignableFrom(type))
1818
{
1919
return true;
2020
}
Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
using System;
1+
using System.Collections;
22
using System.Collections.Generic;
33
using System.Globalization;
4-
using NSubstitute.Exceptions;
54
using NUnit.Framework;
65

76
namespace NSubstitute.Acceptance.Specs;
@@ -11,31 +10,126 @@ public class GenericArguments
1110
{
1211
public interface ISomethingWithGenerics
1312
{
14-
void Log<TState>(int level, TState state);
13+
void SomeAction<TState>(int level, TState state);
14+
string SomeFunction<TState>(int level, TState state);
15+
void SomeActionWithGenericConstraints<TState>(int level, TState state) where TState : IEnumerable<int>;
16+
string SomeFunctionWithGenericConstraints<TState>(int level, TState state) where TState : IEnumerable<int>;
17+
}
18+
19+
public abstract class MyAnyType : IEnumerable<int>, Arg.AnyType
20+
{
21+
public abstract IEnumerator<int> GetEnumerator();
22+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
1523
}
1624

1725
[Test]
18-
public void Return_result_for_any_argument()
26+
public void Any_matcher_works_with_AnyType()
27+
{
28+
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
29+
30+
something.SomeAction(7, 3409);
31+
32+
something.Received().SomeAction(Arg.Any<int>(), Arg.Any<Arg.AnyType>());
33+
something.Received().SomeAction(7, 3409);
34+
}
35+
36+
[Test]
37+
public void When_Do_works_with_AnyType()
1938
{
20-
string argDoResult = null;
2139
int? whenDoResult = null;
2240
bool whenDoCalled = false;
2341
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
24-
something.Log(Arg.Any<int>(), Arg.Do<Arg.AnyType>(a => argDoResult = ">>" + ((int)a).ToString("P", CultureInfo.InvariantCulture)));
2542
something
26-
.When(substitute => substitute.Log(Arg.Any<int>(), Arg.Any<Arg.AnyType>()))
43+
.When(substitute => substitute.SomeAction(Arg.Any<int>(), Arg.Any<Arg.AnyType>()))
2744
.Do(info =>
2845
{
2946
whenDoResult = info.ArgAt<int>(1);
3047
whenDoCalled = true;
3148
});
3249

33-
something.Log(7, 3409);
50+
something.SomeAction(7, 3409);
3451

35-
something.Received().Log(Arg.Any<int>(), Arg.Any<Arg.AnyType>());
36-
something.Received().Log(7, 3409);
3752
Assert.That(whenDoCalled, Is.True);
38-
Assert.That(argDoResult, Is.EqualTo(">>340,900.00 %"));
3953
Assert.That(whenDoResult, Is.EqualTo(3409));
4054
}
55+
56+
[Test]
57+
public void ArgDo_works_with_AnyType()
58+
{
59+
string argDoResult = null;
60+
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
61+
something.SomeAction(Arg.Any<int>(), Arg.Do<Arg.AnyType>(a => argDoResult = ">>" + ((int)a).ToString("P", CultureInfo.InvariantCulture)));
62+
63+
something.SomeAction(7, 3409);
64+
65+
Assert.That(argDoResult, Is.EqualTo(">>340,900.00 %"));
66+
}
67+
68+
[Test]
69+
public void Is_matcher_works_with_AnyType()
70+
{
71+
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
72+
73+
something.SomeFunction(Arg.Any<int>(), Arg.Is<Arg.AnyType>(a => (int)a == 3409)).Returns("matched");
74+
75+
var result = something.SomeFunction(7, 3409);
76+
77+
Assert.That(result, Is.EqualTo("matched"));
78+
}
79+
80+
[Test]
81+
public void Any_matcher_works_with_AnyType_and_constraints()
82+
{
83+
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
84+
var state = new[] {3409};
85+
something.SomeActionWithGenericConstraints(7, state);
86+
87+
something.Received().SomeActionWithGenericConstraints(Arg.Any<int>(), Arg.Any<MyAnyType>());
88+
something.Received().SomeActionWithGenericConstraints(7, state);
89+
}
90+
91+
[Test]
92+
public void When_Do_works_with_AnyType_and_constraints()
93+
{
94+
int[] whenDoResult = null;
95+
bool whenDoCalled = false;
96+
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
97+
something
98+
.When(substitute => substitute.SomeActionWithGenericConstraints(Arg.Any<int>(), Arg.Any<MyAnyType>()))
99+
.Do(info =>
100+
{
101+
whenDoResult = info.ArgAt<int[]>(1);
102+
whenDoCalled = true;
103+
});
104+
105+
var expected = new[] {3409};
106+
something.SomeActionWithGenericConstraints(7, expected);
107+
108+
Assert.That(whenDoCalled, Is.True);
109+
Assert.That(whenDoResult, Is.EqualTo(expected));
110+
}
111+
112+
[Test]
113+
public void ArgDo_works_with_AnyType_and_constraints()
114+
{
115+
string argDoResult = null;
116+
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
117+
something.SomeActionWithGenericConstraints(Arg.Any<int>(), Arg.Do<MyAnyType>(a => argDoResult = ">>" + ((int[])a)[0].ToString("P", CultureInfo.InvariantCulture)));
118+
119+
something.SomeActionWithGenericConstraints(7, new[] {3409});
120+
121+
Assert.That(argDoResult, Is.EqualTo(">>340,900.00 %"));
122+
}
123+
124+
[Test]
125+
public void Is_matcher_works_with_AnyType_and_constraints()
126+
{
127+
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
128+
129+
something.SomeFunctionWithGenericConstraints(Arg.Any<int>(), Arg.Is<MyAnyType>(a => ((int[])a)[0] == 3409)).Returns("matched");
130+
131+
var result = something.SomeFunctionWithGenericConstraints(7, new[] {3409});
132+
133+
Assert.That(result, Is.EqualTo("matched"));
134+
}
41135
}

0 commit comments

Comments
 (0)