在Moq中模拟泛型方法而不指定T.

时间:2013-11-19 13:08:58

标签: c# .net generics mocking moq

我有一个方法接口如下:

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

我想模拟包含此方法的类,而不必为可用于的每种类型指定安装方法。理想情况下,我只是希望它返回new mock<T>.Object

我如何实现这一目标?

似乎我的解释不清楚。这是一个例子 - 当我指定T(这里是字符串)时,这是可能的:

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

我想要达到的目标是:

[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.
}

测试对象的某些方法可能会为三种或四种不同类型调用reserve。如果我必须设置所有类型,我必须为每个测试编写大量的设置代码。但是在一次测试中,我并不关心所有这些,我只需要非空的模拟对象,除了我实际测试的那个(我很乐意写一个更复杂的设置)。

6 个答案:

答案 0 :(得分:20)

在Moq 4.13中,他们引入了It.IsAnyType类型,可用于模拟通用方法。例如

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

答案 1 :(得分:19)

只需这样做:

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

  ...
}

由于您的模拟没有行为Strict,因此对您尚未设置的调用感到满意。在这种情况下,只返回“默认”。然后

DefaultValue.Mock

确保此“默认”是适当类型的新Mock<>,而不仅仅是空引用。

此处的限制是您无法控制(例如,进行特殊设置)返回的单个“子模拟”。

答案 2 :(得分:18)

除非我误解了你的需要,否则你可以建立一个这样的方法:

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

答案 3 :(得分:5)

我找到了一种我认为更接近你想要的替代方案。无论如何它对我有用,所以这里。我们的想法是创建一个几乎纯粹抽象的中间类,并实现您的界面。不抽象的部分是Moq无法处理的部分。 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
}

现在你可以模拟RepoFake而不是IRepo。除了您在ReserveProxy而不是Reserve上编写设置外,所有内容都相同。如果要根据类型执行断言,可以处理回调,尽管Type ReserveProxy参数完全是可选的。

答案 4 :(得分:0)

这是实现目的的一种方法。如果您在IRepo中使用的所有类都继承自单个基类,则可以按原样使用它,而不必更新它。

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

否则,如果您没有基类,则可以修改SetupGenericReserve以不使用TBase类型参数,而只是创建您要设置的所有类型的列表,如下所示:

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

注意:这是为ASP.NET Core编写的,但除了GetDerivedTypes方法之外,还应该适用于其他版本。

答案 5 :(得分:0)

我找不到有关使用Moq使用通用模拟方法模拟通用方法的任何信息。到目前为止,我发现的唯一发现是对每个特定类型的泛型方法进行了模拟,这无济于事,因为通常来说,您无法真正预先预知泛型参数的所有可能情况/变化。

因此,我通过创建该接口的虚假/空实现而不是使用Moq来解决此类问题。

在您的情况下,它看起来像这样:

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

public class FakeRepo : IRepo
{
    public IA<T> Reserve<T>()
    {
        // your logic here
    }
}

然后,只需注入该伪造的实现来代替IRepo的使用。