2013-11-19 4 views
22

У меня есть интерфейс с методом следующим образом:Mocking общих методов в Moq без указания T

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

Я хотел бы издеваться классом, который содержит этот метод без указания методы настройки для каждого типа он мог для использования. В идеале я бы просто хотел вернуть new mock<T>.Object.

Как это достичь?

Кажется, мое объяснение было неясным. Вот пример - сейчас это возможно, когда я указать Т (здесь, строка):

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

Некоторые методы из тестируемый объект может вызвать резерв для трех или четырех разных типов. Если мне нужно настроить все типы, я должен написать много кода установки для каждого теста. Но в одном тесте я не беспокоюсь обо всех них, мне просто нужны ненулевые штудированные объекты, кроме тех, которые я фактически тестировал (и для которых я с радостью напишу более сложную настройку).

ответ

14

Если я не хочу понять то, что вам нужно, вы можете создать метод, как это:

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

Я пробовал это, но я продолжаю получать «тип или пространство имен« Т »не может быть найдено», когда я пытаюсь скомпилировать это. – Wilbert

+0

@Wilbert, какую версию .NET Framework вы используете? –

+0

Хорошо, различие, конечно, в том, что вы положили все это в общую функцию. Мне нужно поставить его в не-общую функцию. – Wilbert

12

Просто сделать это:

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

    ... 
} 

Поскольку ваш макет не имеет поведение Strict, это будет будьте довольны звонками, которые вы даже не настроили. В этом случае «default» просто возвращается. Тогда

DefaultValue.Mock 

гарантирует, что это «по умолчанию» является новым Mock<> соответствующего типа, а не только нулевой ссылки.

Ограничение здесь заключается в том, что вы не можете контролировать (например, производить специальные настройки) отдельные «подмаки», которые возвращаются.

4

Я нашел альтернативу, которая, я думаю, приближается к тому, что вы хотите. Во всяком случае, это было полезно для меня, так что здесь. Идея состоит в том, чтобы создать промежуточный класс, который является почти чисто абстрактным и реализует ваш интерфейс. Часть, которая не является абстрактной, является частью, которую 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 является необязательным.

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 Ядра, но должно работать и в других версиях для метода GetDerivedTypes исключением.

Смежные вопросы