FakeItEasy, Подделка родительского виртуального метода из дочернего класса
Я пытаюсь подделать вызов родительского общедоступного метода виртуальной проверки от ребенка безуспешно (с помощью FakeItEasy
, У меня есть базовый класс, который проверяет простые команды для похожих командных классов (для простоты я уменьшил код):
public class CanSetSettings<TSettings> : IValidationhandler<TSettings> where TSettings : ISetting
{
protected readonly IFooRepository Repository;
protected List<ValidationResult> Results;
public CanSetSettings(IFooRepository repository)
{
if (Repository== null)
throw new ArgumentNullException("IFooRepository ", "IFooRepository is needed to validate the command.");
repository = repository;
Results = new List<ValidationResult>();
}
public virtual ICollection<ValidationResult> Validate(TSettings settings)
{
if (settings == null)
{
Results.Add(new ValidationResult("Settings", "The command to validate cannot be missing."));
return Results;
}
if (Repository.Query(settings.Location) == null)
Results.Add(new ValidationResult("Location", "No Location was found for your settings."));
return Results;
}
Затем у меня есть дочерние классы, которые наследуются от этого базового класса, а затем реализует их конкретную логику путем переопределения Validate
(упростил мой код).
public class CanSetSpecificSetting : CanSetSettings<SetSpecificSettings>, IValidationhandler<SetSpecificSettings>
{
public CanSetSpecificSetting (IFooRepository repo)
: base(repo)
{ }
public override ICollection<ValidationResult> Validate(SetSpecificSettings command)
{
base.Validate(command); // TODO determine if this call was made
// ... other logic here
return results;
}
}
Я попробовал это в моем модульном тесте, и он только настраивает вызов метода для дочернего класса, и я не могу настроить родительский класс. Как настроить фальшивку для вызова дочернего класса и фальсификации метода родительского базового класса? Спасибо.
var _repository = A.Fake<IFooRepository>();
var _validator = A.Fake<CanSetSpecificSetting>(opt => opt.WithArgumentsForConstructor(new object[] { _repository }));
A.CallTo(() => _validator.Validate(_cmd)).CallsBaseMethod().Once();
_validator.Validate(_cmd);
// this passes, but only because it knows about the child call
A.CallTo(() => _validator.Validate(_cmd)).MustHaveHappened(Repeated.Exactly.Once);
2 ответа
Нет, я не думаю, что вы можете легко сделать то, что вы собираетесь делать, используя FakeItEasy. Я даже не думаю, что вы должны это делать.
НО вы можете достичь чего-то похожего, заключив базовый вызов в шаблонный метод в вашем подклассе. Просто поменяй CanSetSpecificSetting
, как это:
public class CanSetSpecificSetting : CanSetSettings<SetSpecificSettings>, IValidationhandler<SetSpecificSettings>
{
public CanSetSpecificSetting (IFooRepository repo)
: base(repo)
{ }
public override ICollection<ValidationResult> Validate(SetSpecificSettings command)
{
BaseValidate(command); // << Instead of calling base.Validate(command).
// ... other logic here
return results;
}
// This is the template method. You MUST declare it as virtual.
protected virtual ICollection<ValidationResult> BaseValidate(SetSpecificSettings command)
{
return base.Validate(command);
}
}
А затем измените свой тест следующим образом:
var _repository = A.Fake<IFooRepository>();
var _validator = A.Fake<CanSetSpecificSetting>(opt => opt.WithArgumentsForConstructor(new object[] { _repository }));
A.CallTo(() => _validator.Validate(_cmd)).CallsBaseMethod().Once();
// *** ADD THIS LINE *** must configure it this way because the template method is protected - we don't want to make it public!
A.CallTo(_validator).WhereMethod(x => x.Name == "BaseValidate").Returns("whatever-you-want-to-return");
_validator.Validate(_cmd);
A.CallTo(() => _validator.Validate(_cmd)).MustHaveHappened(Repeated.Exactly.Once);
Опять же, это безобразно. Единственный случай, когда я могу себе представить, что это будет нормально, - это добавление тестов в некоторый устаревший код, который вы собираетесь (и будете) реорганизовать как можно скорее.
Надеюсь, поможет.
Интересный вопрос. Разновидность использования A.CallTo() в FakeItEasy для другого метода в том же объекте, за исключением того, что это не "другой метод в том же объекте", а "(почти) тот же метод в том же объекте".
Я не думаю, что это возможно. Когда FakeItEasy делает свою подделку, он создает подкласс, который имеет свое собственное определение Validate
, Мы можем опросить этот метод, но нет способа изменить поведение любого из базовых классов, и поэтому мы не можем спросить класс, была ли вызвана его база.
Я думаю, что этот подход к тестированию валидаторов немного необычен и рекомендовал бы другой путь. Если у вас есть возможность реструктурировать производственный код, вы можете составить валидаторы вместо того, чтобы определять их в иерархии. Или вы можете использовать базовую (общую) функциональность в другом методе, который вызывается Validate. Тогда вы могли бы опросить этот метод, хотя я не рекомендую этот последний подход.
Если вы не можете (или не хотите) реструктурировать производственный код, альтернативой может быть проверка общего поведения конкретных валидаторов. Вместо того, чтобы видеть, если CanSetSpecificSetting
вызывает свою базу, попробуйте проверить, правильно ли она ведет себя в различных условиях, например, когда хранилище имеет значение null. Мало того, что это легче (возможно) проверить, этот подход даст лучшие результаты - гарантирует, что вся ваша система ведет себя так, как вы этого хотите, а не только то, что некоторые методы вызывают определенные родительские функции.
В зависимости от вашей среды тестирования может быть довольно просто сгенерировать все эти тесты, параметризовав базовые тестовые примеры и затем выполнив их для каждой конкретной реализации.