-
-
Notifications
You must be signed in to change notification settings - Fork 802
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 support for generic type argument matchers (It.IsAnyType and custom matchers) #908
Add support for generic type argument matchers (It.IsAnyType and custom matchers) #908
Conversation
src/Moq/ExpressionExtensions.cs
Outdated
{ | ||
foreach (var typeArgument in methodCallExpression.Method.GetGenericArguments()) | ||
{ | ||
if (typeArgument.IsTypeMatcher()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this loop is going to run for every generic method Moq encounters, it might be a good idea to cache information about whether or not some Type
is a type matcher.
src/Moq/Guard.cs
Outdated
@@ -16,6 +16,18 @@ namespace Moq | |||
[DebuggerStepThrough] | |||
internal static class Guard | |||
{ | |||
public static void HasDefaultConstructor(Type type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be useful in MockDefaultValueProvider
, too. Today, DefaultValue.Mock
will simply attempt to mock types even if they don't have a default constructor, resulting in a DynamicProxy exception (which could be hidden).
An alternative would be to instead add a type.HasDefaultConstructor()
check inside type.IsMockable()
. That way, MockDefaultValueProvider
would no longer attempt to mock default ctor-less types and delegate to DefaultValue.Empty
.
Nice! Thanks for caring about this issue so long after! Will take a look at the code and take it out for a spin tomorrow! |
6805fc8
to
bb53067
Compare
It just occurred to me that we could special-case
|
where the callbacks will receive the current `IInvocation` happening. This allows these callbacks to query the generic type arguments.
bb53067
to
8955f5f
Compare
8955f5f
to
3610193
Compare
3610193
to
10bc369
Compare
10bc369
to
1d572e5
Compare
These commits add support for generic type argument matching. 😎
For starters, the only built-in type matcher is
It.IsAnyType
:Creating a custom type matcher:
The type matcher infrastructure is designed to be fully extensible:
This allows matchers to satisfy generic type contraints:
When the type is constrained to something that won't allow you to implement
ITypeMatcher
, there's[TypeMatcher]
:Accessing the type arguments in
Callback
andReturns
:(@michal-ciechan, this one is for you: You requested that some "invocation context" be injected into
Callback
andReturns
callbacks, it turns out Moq already has a suitable type for that:IInvocation
.)In order for
Callback
andReturns
to be able to know which type arguments were used, there are two new overloads that accept aInvocationAction
andInvocationFunc
, respectively:These two types are designed as delegate type look-alikes, however they aren't actually delegates. This was necessary to prevent the C# compiler from starting to pick the wrong
Callback
andReturns
overloads in everyday use cases.Matching arguments of a generic type:
Things left to do:
Add support for
It.IsAny<It.IsAnyType>
. Or, more generally speaking:It.IsAny<TTypeMatcher>
,It.IsNotNull<TTypeMatcher>
, etc.If some or all of the above is implemented, add tests for the
Capture
code paths. (More specifically, I am worrying about the type conversions(T)
hidden inside theMatch
class. Those will generally fail whenT
is a type matcher.)Update: This is only partially fulfilled. While
Capture.*
shouldn't throw,CaptureMatch<T>
won't work just yet. This is left as a TODO for another time.Add support for composite types making use of type matchers, e.g.It.IsAnyType?
,It.IsAnyType[]
,It.IsAnyType[,]
,IEnumerable<It.IsAnyType>
,ref
/in
/out It.IsAnyType
, etc. Same for custom matcher types. It's possible that this cannot be made to work with custom argument matchers.Update: While that would be great, it would also negatively affect performance as every type parameter would have to be decomposed just to discover whether it includes a type matcher. For now, we're going the opposite direction and try to make type matcher discovery as fast as possible.
Closes #343.
/cc @michal-ciechan, @oddbear, @kzu