FakeItEasy,假孩子班的父母虚拟方法

时间:2014-12-19 20:35:06

标签: c# unit-testing inheritance 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 个答案:

答案 0 :(得分: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);

同样,这是丑陋的。我能想象的唯一一个例子就是将测试添加到您希望(并且将会)尽快重构的一些遗留代码中。

希望它有所帮助。

答案 1 :(得分:1)

有趣的问题。 Use FakeItEasy's A.CallTo() on another method in same object的变体,除了它不是“同一个对象中的另一个方法”之外,它是“(几乎)在同一个对象中的相同方法”。

我不认为这是可能的。当FakeItEasy伪造时,它会创建一个具有自己Validate定义的子类。我们可以询问那个方法,但是没有办法修改任何基类的行为,因此我们不能询问类是否调用了它的基础。

我认为这种测试验证器的方法有点不寻常,并建议采用不同的方法。如果您可以选择重构生产代码,则可以编写验证器而不是在层次结构中定义验证器。或者您可以将基本(公共)功能驻留在由Validate调用的其他方法中。然后你可以询问那个方法,虽然我不建议采用后一种方法。

如果您不能(或不愿意)重构生产代码,则可以选择测试具体验证器的整体行为。不要查看CanSetSpecificSetting是否调用它的基础,而是考虑检查它是否在不同条件下正常运行,例如当存储库为空时。这不仅更容易(可能)验证,该方法将提供更好的结果 - 确保您的整个系统按照您希望的方式运行,而不仅仅是某些方法确定调用其父级。

根据您的测试框架,通过参数化基本测试用例然后为每个具体实现执行它们,可以非常容易地生成所有这些测试。