我有一个方法接口如下:
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。如果我必须设置所有类型,我必须为每个测试编写大量的设置代码。但是在一次测试中,我并不关心所有这些,我只需要非空的模拟对象,除了我实际测试的那个(我很乐意写一个更复杂的设置)。
答案 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的使用。