【问题标题】:Why does FakeItEasy throw this exception, and why does making the method virtual fix it?为什么 FakeItEasy 会抛出这个异常,为什么让方法 virtual 修复它?
【发布时间】:2014-09-02 08:49:35
【问题描述】:

我有一个测试(代码如下)来测试 Method1 调用 Method2。我得到的例外是

当前代理生成器无法拦截指定的方法 原因如下: - 无法拦截密封方法。

被测方法本身并未密封。但是,它确实对密封类有 依赖 (我无法为其创建包装器以正确模拟它的第三方类 - 另一个问题的另一个主题)。无论哪种方式,此时我并不是要 FakeItEasy 模拟密封类。在调试我的测试时,当调用依赖项时,我可以清楚地看到正在生成一个真实的对象,而不是假的。

然而,鉴于错误消息,我觉得它可能以某种方式相关。

此外,我通过random blog post 发现,将方法设为虚拟可以解决问题,从而使测试通过。我试了一下,它奏效了。但我不明白 为什么 它修复了它,无论如何,让方法保持虚拟对我来说没有意义。就我而言,被测试的班级没有自己的孩子,即;没有孩子可以覆盖它的方法,所以我看不出有什么理由让它成为虚拟的。

我认为我没有理由将方法设为虚拟是不是错了?
FakeItEasy 是否以某种方式试图模拟那个密封类?

我真的不确定如何进行此测试。

我的测试

[SetUp]
public void SetUp()
{
    // Arrange
    _service2 = A.Fake<Service2>(x => x.WithArgumentsForConstructor(
                                        () => new Service2()));
    _service1 = A.Fake<Service1>(x => x.WithArgumentsForConstructor(
                                        () => new Service1(_service2)));
}

[Test]
public void when_Method1_executes_it_calls_Method2()
{
    // Act
    result = _service1.Method1();

    // Assert
     A.CallTo(() => _service2.Method2())
                                   .WithAnyArguments()
                                   .MustHaveHappened();
}


相关方法

public class Service1 : IService1
{
    private readonly IService2 _service2;
    public Service1(IService2 service2)
    { 
        _service2 = service2; 
    }

    public bool Method1()
    {
        using (var dependency = new MyDependency()) // third party sealed class
        {
        }

        var x = _service2.Method2();            
    }
}   


public class Service2 : IService2
{
    public bool Method2() // making this virtual fixes the FakeItEasy exception
    {            
    }
}

【问题讨论】:

    标签: c# asp.net-mvc testing fakeiteasy


    【解决方案1】:

    我这两天一直在研究一个几乎相同的问题。 Blair Conrad 的上述在接口级别进行伪装的解决方案对我有用,而且实际上也很有意义:

    如果被测试的类不依赖于另一个类,那么测试也不应该。因此,您伪造了接口,而不是实现了接口的类。

    【讨论】:

    • 我想这应该是对引用答案的评论,而不是答案本身,因为它不提供解决方案。
    【解决方案2】:

    虽然通常应用于类范围,但在这种情况下,sealed 指的是无法覆盖相关方法。仅当方法是 override 时,将 sealed 与方法一起使用才有效 - 但是,首先不是虚拟的方法不能被覆盖,因此它们本身是隐式密封的。

    这个错误指的是它不能接受非virtual 方法,因为它正在动态创建一个从给定类继承的类来执行这些拦截。在这样的级别上,它既不能确定非虚拟方法和密封方法之间的区别,也不需要 - 它不能覆盖,因此不能插入适当的拦截。

    【讨论】:

    • 很好的答案。不过需要澄清一点:sealed 对方法有效。它具有取消可能已应用于父方法的任何虚拟的效果。正如你所说,FakeItEasy 使用 Castle Dynamic Proxy 创建一个继承自 Service2 的类。执行A.CallTo时,发现方法不能被覆盖,所以FakeItEasy无法拦截调用。
    • 好吧,看来我得再探索一下了。感谢您的澄清,我会整合它!
    • 谢谢 - 我实际上没有意识到 FakeItEasy 会像这样动态创建类。听起来我需要了解它在幕后是如何工作的。希望我能找到一些对我的经验水平来说足够清楚的解释。我特别好奇为什么我以前没有遇到过这个问题——因为我测试过的其他方法都不是虚拟的——以及 Service2 的不同之处。我有类似的设置来测试 MVC 操作方法 - 注入带有服务类的控制器,并验证是否对服务类中的方法进行了调用。
    • 进一步考虑,我认为我之前没有看到这个错误的原因是因为我碰巧在我的其他测试中注入了接口,如另一个答案中所述。
    【解决方案3】:

    Aravol 的回答很棒。我敦促您接受它和/或投票。

    不过,还有另一种方法。将_service2 伪装成IService2

    那么您不必更改任何方法的签名(接口方法始终是可覆盖/可拦截的)。一般来说,伪造接口接口比具体(甚至抽象)类更容易,并且它具有实际测试您的协作类可以与接口一起使用的良好效果,而不必仅与接口的特定实现一起使用。

    虽然我在这里,这部分与您的错误并没有真正的关系,但可能有助于使您的代码更清晰一些,因为您正在测试Service1,我不会伪造它;使用实际的Service1 实例。这让读者清楚地知道实际测试的是什么。伪造被测系统被广泛认为是一种代码异味。

    如果您碰巧兼顾了这两点,您的 SetUp 看起来会更像这样:

    [SetUp]
    public void SetUp()
    {
        // Arrange
        _service2 = A.Fake<IService2>();
        _service1 = new Service1(_service2);
    }
    

    你的测试应该通过了。

    【讨论】:

    • 谢谢 - 我很感激不必为了我的测试而使我的方法虚拟化。并感谢关于不伪造 Service1 的建议 - 我经常不清楚应该/不应该嘲笑什么。
    猜你喜欢
    • 1970-01-01
    • 2011-07-19
    • 1970-01-01
    • 1970-01-01
    • 2015-12-24
    • 2022-01-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多