Может ли NSubstitute имитировать возвращаемое значение, основываясь на аргументе интерфейса, а не на конкретном типе?

У меня есть сущность и валидатор:

public class Customer : IEntity { /* ... */ }
public class CustomerValidator : IValidator<Customer> { /* ... */ }

Я хочу издеваться над фабрикой валидаторов:

public interface IValidatorFactory
{
  IValidator<TEntity> create<TEntity>(TEntity entity) where TEntity : class, IEntity;
}

Мой макет:

var mockFactory = Substitute.For<IValidatorFactory>();
mockFactory.create(Arg.Any<Customer>()).Returns(m => new CustomerValidator());

Это работает, когда аргумент Customer,

Но код, который я тестирую, проходит IEntity, так что макет не работает.

Могу ли я переписать макет для обработки аргумента, типизированного для интерфейса, а не для конкретного класса?

1 ответ

Решение

Вы имеете дело с обобщениями, которые по умолчанию не сохраняют наследование параметров типа. Параметры универсального типа по умолчанию не изменяются. Это означает, что вы не можете вернуть экземпляр IValidator<Customer> как IValidator<IEntity>, даже если Customer происходит от IEntity, Вам нужно явно указать отношение контравариантности, которое позволит вам использовать более общий тип IValidator<IEntity> чем изначально создан IValidator<Customer>, Просто пометьте параметр типа с помощью out ключевое слово:

public interface IValidator<out T> where T : IEntity { }

Вы можете прочитать о "Ковариантности и Контравариантности в Обобщениях"

Вот полный пример:

public interface IValidatorFactory
{
    IValidator<TEntity> create<TEntity>(TEntity entity) where TEntity : class, IEntity;
}

public interface IEntity { }
public interface IValidator<out T> { }
public class Customer : IEntity { }
public class CustomerValidator : IValidator<Customer> { }

[Test]
public void SO_43360005()
{
    var sub = Substitute.For<IValidatorFactory>();
    sub.create(Arg.Is<IEntity>(x => x is Customer)).Returns(_ => new CustomerValidator());
}
Другие вопросы по тегам