Mocking generic methods in Moq without specifying T

68,567

Solution 1

Unless I'm misunderstand what you need, you could build a method like this:

private Mock<IRepo> MockObject<T>()
{
    var mock = new Mock<IRepo>();
    return mock.Setup(pa => pa.Reserve<T>())
        .Returns(new Mock<IA<T>>().Object).Object;
}

Solution 2

In Moq 4.13 they introduced the It.IsAnyType type which you can using to mock generic methods. E.g.

public interface IFoo
{
    bool M1<T>();
    bool M2<T>(T arg);
}

var mock = new Mock<IFoo>();
// matches any type argument:
mock.Setup(m => m.M1<It.IsAnyType>()).Returns(true);

// matches only type arguments that are subtypes of / implement T:
mock.Setup(m => m.M1<It.IsSubtype<T>>()).Returns(true);

// use of type matchers is allowed in the argument list:
mock.Setup(m => m.M2(It.IsAny<It.IsAnyType>())).Returns(true);
mock.Setup(m => m.M2(It.IsAny<It.IsSubtype<T>>())).Returns(true);

Solution 3

Simply do this:

[TestMethod]
public void ExampleTest()
{
  var mock = new Mock<IRepo> { DefaultValue = DefaultValue.Mock, };
  // no setups needed!

  ...
}

Since your mock does not have behavior Strict, it will be happy with calls that you haven't even set up. In that case a "default" is simply returned. Then

DefaultValue.Mock

ensures that this "default" is a new Mock<> of appropriate type, instead of just a null reference.

The limitation here is that you cannot control (e.g. make special setups on) the individual "sub-mocks" that are returned.

Solution 4

I have found an alternative that I think gets closer to what you want. Anyway it was useful for me so here goes. The idea is to create an intermediate class which is almost purely abstract, and implements your interface. The part which is not abstract is the part Moq can't handle. E.g.

public abstract class RepoFake : IRepo
{
    public IA<T> Reserve<T>()
    {
        return (IA<T>)ReserveProxy(typeof(T));
    }

    // This will be mocked, you can call Setup with it
    public abstract object ReserveProxy(Type t);

    // TODO: add abstract implementations of any other interface members so they can be mocked
}

Now you can mock RepoFake instead of IRepo. Everything works the same except for you write your setups on ReserveProxy instead of Reserve. You can handle the Callback if you want to perform assertions based on type, though the Type parameter to ReserveProxy is totally optional.

Solution 5

Here's one way to do it which seems to work. If all the classes you're using in IRepo inherit from a single base class you can use this as-is and never have to update it.

public Mock<IRepo> SetupGenericReserve<TBase>() where TBase : class
{
    var mock = new Mock<IRepo>();
    var types = GetDerivedTypes<TBase>();
    var setupMethod = this.GetType().GetMethod("Setup");

    foreach (var type in types)
    {
        var genericMethod = setupMethod.MakeGenericMethod(type)
            .Invoke(null,new[] { mock });
    }

    return mock;
}

public void Setup<TDerived>(Mock<IRepo> mock) where TDerived : class
{
    // Make this return whatever you want. Can also return another mock
    mock.Setup(x => x.Reserve<TDerived>())
        .Returns(new IA<TDerived>());
}

public IEnumerable<Type> GetDerivedTypes<T>() where T : class
{
    var types = new List<Type>();
    var myType = typeof(T);

    var assemblyTypes = myType.GetTypeInfo().Assembly.GetTypes();

    var applicableTypes = assemblyTypes
        .Where(x => x.GetTypeInfo().IsClass 
                && !x.GetTypeInfo().IsAbstract 
                 && x.GetTypeInfo().IsSubclassOf(myType));

    foreach (var type in applicableTypes)
    {
        types.Add(type);
    }

    return types;
}

Otherwise, if you don't have a base class you can modify the SetupGenericReserve to not use the TBase type parameter and instead just create a list of all the types you want to set up, something like this:

public IEnumerable<Type> Alternate()
{
    return new [] 
    {
        MyClassA.GetType(),
        MyClassB.GetType()
    }
}

Note: This is written for ASP.NET Core, but should work in other versions except for the GetDerivedTypes method.

Share:
68,567
Wilbert
Author by

Wilbert

Updated on July 05, 2022

Comments

  • Wilbert
    Wilbert almost 2 years

    I have an interface with a method as follows:

    public interface IRepo
    {
        IA<T> Reserve<T>();
    }
    

    I would like to mock the class that contains this method without having to specify Setup methods for every type it could be used for. Ideally, I'd just like it to return a new mock<T>.Object.

    How do I achieve this?

    It seems my explanation was unclear. Here's an example - this is possible right now, when I specify the T (here, string):

    [TestMethod]
    public void ExampleTest()
    {
        var mock = new Mock<IRepo>();
        mock.Setup(pa => pa.Reserve<string>()).Returns(new Mock<IA<string>>().Object);
    }
    

    What I would like to achieve is something like this:

    [TestMethod]
    public void ExampleTest()
    {
        var mock = new Mock<IRepo>();
        mock.Setup(pa => pa.Reserve<T>()).Returns(new Mock<IA<T>>().Object);
        // of course T doesn't exist here. But I would like to specify all types
        // without having to repeat the .Setup(...) line for each of them.
    }
    

    Some methods of the object under test might call reserve for three or four different types. If I have to setup all the types I have to write a lot of setup code for every test. But in a single test, I am not concerned with all of them, I simply need non-null mocked objects except for the one that I am actually testing (and for which I gladly write a more complex setup).

  • Wilbert
    Wilbert over 10 years
    I tried this, but I keep getting 'the type or namespace 'T' could not be found' when I try to compile this.
  • Wilbert
    Wilbert over 10 years
    Ok, the difference is of course that you put the whole thing in a generic function. I need to put it in a non-generic function.
  • Mike Perrenoud
    Mike Perrenoud over 10 years
    @Wilbert, you can't encapsulate the code you want without making the function generic. With my edits above you can do this now: var mock = MockObject<MyConcreteType>(); and that will give you back a Mock<IRepo> that implements that type.
  • Wilbert
    Wilbert over 10 years
    Yes, I get that, but I need the repo to support a few different types. It seems the only way to achieve this is to write lots of .Setup methods :(
  • Wilbert
    Wilbert over 10 years
    Haha. Ah well, I just hoped there would be something in Moq that saves me the grunt work.
  • Mladen B.
    Mladen B. over 4 years
    This solution is ok when you don't care about null values for properties on those auto-created mock objects, since you can't influence/setup those created objects.
  • Michael Puckett II
    Michael Puckett II over 3 years
    What happened to this? Is it not available anymore?
  • Mark Wells
    Mark Wells almost 3 years
    @MichaelPuckettII No, it still works in Moq 4.15. I think the other answer was just accepted before this existed.
  • Matus
    Matus over 2 years
    this does not work, if the methods you want to set up are returning T