Ответ 1
Если я не понимаю, что вам нужно, вы можете создать такой метод:
private Mock<IRepo> MockObject<T>()
{
var mock = new Mock<IRepo>();
return mock.Setup(pa => pa.Reserve<T>())
.Returns(new Mock<IA<T>>().Object).Object;
}
У меня есть интерфейс со следующим способом:
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.
}
Некоторые методы тестируемого объекта могут быть зарезервированы для трех или четырех разных типов. Если мне нужно настроить все типы, я должен написать много кода установки для каждого теста. Но в одном тесте я не беспокоюсь обо всех них, мне просто нужны ненулевые издеваемые объекты, кроме тех, которые я фактически тестирую (и для которых я с радостью напишу более сложную настройку).
Если я не понимаю, что вам нужно, вы можете создать такой метод:
private Mock<IRepo> MockObject<T>()
{
var mock = new Mock<IRepo>();
return mock.Setup(pa => pa.Reserve<T>())
.Returns(new Mock<IA<T>>().Object).Object;
}
Просто выполните следующее:
[TestMethod]
public void ExampleTest()
{
var mock = new Mock<IRepo> { DefaultValue = DefaultValue.Mock, };
// no setups needed!
...
}
Поскольку ваш макет не имеет поведения Strict
, он будет доволен вызовами, которые вы даже не настроили. В этом случае "default" просто возвращается. Тогда
DefaultValue.Mock
гарантирует, что этот "по умолчанию" представляет собой новый Mock<>
соответствующего типа, а не только нулевую ссылку.
Ограничение здесь заключается в том, что вы не можете управлять (например, делать специальные настройки) отдельными "подмаками", которые возвращаются.
Я нашел альтернативу, которая, по моему мнению, приближается к тому, что вы хотите. Во всяком случае, это было полезно для меня, так что здесь. Идея состоит в том, чтобы создать промежуточный класс, который является почти чисто абстрактным и реализует ваш интерфейс. Часть, которая не является абстрактной, является частью, которую 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
}
Теперь вы можете высмеивать RepoFake
вместо IRepo. Все работает одинаково, за исключением того, что вы пишете свои настройки на ReserveProxy
вместо Reserve
. Вы можете обрабатывать обратный вызов, если вы хотите выполнять утверждения на основе типа, хотя параметр Type
для ReserveProxy
является полностью необязательным.
Вот один из способов сделать это, который, кажется, работает. Если все классы, которые вы используете в 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, но должно работать в других версиях, кроме метода GetDerivedTypes.