Mocking generic methods in Moq without specifying T

≯℡__Kan透↙ 提交于 2019-11-28 06:43:35

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;
}

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.

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!