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

Mocking with generic parameters not properly supported #996

Closed
pasx opened this issue Apr 15, 2020 · 2 comments
Closed

Mocking with generic parameters not properly supported #996

pasx opened this issue Apr 15, 2020 · 2 comments
Labels

Comments

@pasx
Copy link

pasx commented Apr 15, 2020

I am trying to mock a method with generic parameters as described in this PR #908

It works with only the most basic cases showed in the example e.g:

Mock.Setup(p => p.Simple(It.IsAny<It.IsAnyType>())).Callback((object t) => Simple(t));
...
public void Simple<T>(T t) => Console.Writeline(t);

This setup for instance will fail when attempting to cast the string argument to an It.IsAnyType:

Mock.Setup(p => p.Get<It.IsAnyType>(It.IsAny<It.IsAnyType>())).Returns(Get);
...
public T Get<T>(T t) => t;
...
var s = mock.Get("1");

Using object in the setup works:

Mock.Setup(p => p.Get(It.IsAny<object>())).Returns<object>(Get);

But then there is no way to tell which type was actually passed in which makes it of very limited interest.

Can this be improved to support methods with generic return types? Otherwise you may want to make the limitations clearer.

@stakx
Copy link
Contributor

stakx commented Apr 15, 2020

At runtime, there is no such thing as a concrete value of type It.IsAnyType, so no argument will ever be cast from string to It.IsAnyType. The latter is merely a placeholder appearing in setup expressions, beyond that, it doesn't really exist at runtime.

This setup for instance will fail when attempting to cast the string argument to an It.IsAnyType:

Mock.Setup(p => p.Get<It.IsAnyType>(It.IsAny<It.IsAnyType>())).Returns(Get);

Can you please be more precise here? What exactly do you mean by "will fail"? And what is .Returns(Get) supposed to do? What does Get in that particular code location refer to?

All of that being said, It.IsAnyType is fairly advanced. In your case, there's nothing wrong with simply using object instead.

Using object in the setup works: [...] But then there is no way to tell which type was actually passed in which makes it of very limited interest.

Not so: you can pass an InvocationFunc to Returns to get at the IInvocation representing the current call. That allows you to get at the MethodInfo for the invoked method (invocation.Method), the generic type arguments used (invocation.Method.GetGenericArguments()), and the invocation.Arguments; then produce a return value based on all that.

mock.Setup(p => p.Get(It.IsAny<object>())).Returns(new InvocationFunc(invocation => ...));

@stakx
Copy link
Contributor

stakx commented Apr 21, 2020

Apologies, I misread part of your post. I missed the bit defining Get. Well, the problem here isn't Moq but simply that you're trying to do some metaprogramming that clashes with the C# compiler's typechecking. There's no way around doing some reflection. For example:

using Moq;
using System;
using System.Reflection;

public interface IFoo
{
    Func<T, T> Get<T>(T index);
}

class Program
{
    public static T Get<T>(T t) => t;

    static void Main()
    {
        var mock = new Mock<IFoo>();
        mock.Setup(m => m.Get(It.IsAny<It.IsAnyType>()))
            .Returns(new InvocationFunc(invocation =>
            {
                var genericArg = invocation.Method.GetGenericArguments()[0];
                var get = typeof(Program).GetMethod("Get", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(genericArg);
                return Delegate.CreateDelegate(typeof(Func<,>).MakeGenericType(genericArg, genericArg), get);
            }));

        var fn = mock.Object.Get("42");
        fn("42");
    }
}

@stakx stakx closed this as completed Apr 21, 2020
@stakx stakx added the question label Apr 21, 2020
@devlooped devlooped locked and limited conversation to collaborators Sep 5, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants