Подтвердить что ты не робот

Издевательские общие методы в Moq без указания T

У меня есть интерфейс со следующим способом:

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

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

4b9b3361

Ответ 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;
}

Ответ 2

Просто выполните следующее:

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

  ...
}

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

DefaultValue.Mock

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

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

Ответ 3

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

Ответ 4

Вот один из способов сделать это, который, кажется, работает. Если все классы, которые вы используете в 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.