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

Wil*_*ert 43 .net c# generics moq mocking

我有一个方法的接口如下:

public interface IRepo
{
    IA<T> Reserve<T>();
}
Run Code Online (Sandbox Code Playgroud)

我想模拟包含此方法的类,而不必为它可用于的每种类型指定安装方法.理想情况下,我只是喜欢它返回一个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);
}
Run Code Online (Sandbox Code Playgroud)

我想要实现的是这样的:

[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.
}
Run Code Online (Sandbox Code Playgroud)

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

小智 37

在 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);
Run Code Online (Sandbox Code Playgroud)

  • 如果您要设置的方法返回 T,则这不起作用 (16认同)
  • 这是怎么回事?是不是已经不可用了? (2认同)

Jep*_*sen 20

只需这样做:

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

  ...
}
Run Code Online (Sandbox Code Playgroud)

由于你的模拟没有行为Strict,它会对你甚至没有设置的调用感到满意.在这种情况下,只返回"默认".然后

DefaultValue.Mock
Run Code Online (Sandbox Code Playgroud)

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

这里的限制是您无法控制(例如,进行特殊设置)返回的单个"子模拟".


Mik*_*oud 17

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

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

  • @Wilbert,是的,单元测试是非常繁琐的工作.* (7认同)
  • 好吧,区别在于您将整个事物放在通用函数中.我需要将它放在非泛型函数中. (4认同)
  • 是的,我明白了,但是我需要存储库来支持几种不同的类型。看来实现此目标的唯一方法是编写许多.Setup方法:( (3认同)

Old*_*eve 6

我发现了一种我认为更接近你想要的替代方案.无论如何它对我有用,所以这里.我们的想法是创建一个几乎纯粹抽象的中间类,并实现您的界面.不抽象的部分是Moq无法处理的部分.例如

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
}
Run Code Online (Sandbox Code Playgroud)

现在你可以模拟RepoFake而不是IRepo.一切都是一样的,除了你写的设置ReserveProxy而不是Reserve.如果要基于类型执行断言,则可以处理回调,尽管Type参数to ReserveProxy是完全可选的.