【问题标题】:Use of SetupSet 'forgets' Method Setup使用 SetupSet '忘记' 方法设置
【发布时间】:2017-07-28 14:20:39
【问题描述】:

当我尝试使用 Moq 解决不同的情况时,我尝试使用 SetupSet 来解决。这发现了另一个潜在问题。

当我在属性上使用 SetupSet 以及在方法上使用设置时,起订量似乎“忘记”了方法上的设置已经完成。

这里是示例代码,很简单:

public class Prancer
{

    public Prancer(bool pIsMale)
    {
        IsMale = pIsMale;
        ExecuteMe();
    }

    private bool _IsMale;
    public virtual bool IsMale
    {
        get { return this._IsMale; }
        private set { this._IsMale = value; }
    }

    private bool _Antlers;
    public virtual bool Antlers
    {
        get { return this._Antlers; }
        set
        {
            this._Antlers = value;
        }
    }

    public virtual void ExecuteMe()
    {
        throw new Exception("Why am I here?");
    }
}

这里是单元测试:

public class PrancerTests
{
    [Fact]
    public void Antlers_NoSetup()
    {
        // Arrange

        // create mock of class under test
        var sut = new Mock<Prancer>(true) { CallBase = true };
        sut.Setup(x => x.ExecuteMe()); // nullify

        // Act
        sut.Object.Antlers = true;

        // Assert
        sut.VerifySet(x => x.Antlers = true);
    }

    [Fact]
    public void Antlers_SetupProperty()
    {
        // Arrange

        // create mock of class under test
        var sut = new Mock<Prancer>(true) { CallBase = true };
        sut.SetupProperty(x => x.Antlers, false);
        sut.Setup(x => x.ExecuteMe()); // nullify

        // Act
        sut.Object.Antlers = true;

        // Assert
        sut.VerifySet(x => x.Antlers = true);
    }

    [Fact]
    public void Antlers_SetupSet()
    {
        // Arrange

        // create mock of class under test
        var sut = new Mock<Prancer>(true) { CallBase = true };
        sut.SetupSet(x => x.Antlers = true);
        sut.Setup(x => x.ExecuteMe()); // nullify

        // Act
        sut.Object.Antlers = true;

        // Assert
        sut.VerifySet(x => x.Antlers = true);
    }

}

我使用 SetupSet 的单元测试报告了 ExecuteMe() 方法中抛出的异常(“我为什么在这里?”),这证明了 ExecuteMe() 方法即使在存在 Setup(x => x.ExecuteMe ()) 来防止它。其他两个单元测试通过(显然不执行 ExecuteMe())。

我什至尝试为 ExecuteMe() 设置回调,但结果相同。我还颠倒了 Setup 和 SetupSet 的顺序(在代码中),但无济于事。

任何想法为什么 SetupSet 可能会影响方法 Setup?

【问题讨论】:

  • 它没有,我找不到说明原因的文档,但是SetupSet 会通过构造函数,而您的其他设置/验证都没有。构造函数中调用的虚拟方法可能会导致奇怪的行为,但这看起来SetupSet 也有奇怪的地方

标签: c# unit-testing moq


【解决方案1】:

任何想法为什么 SetupSet 可能会影响方法 Setup?

我相信这是 Moq 中的一个错误。请你这么好心file an issue at Moq's GitHub repository moq/moq4好吗? (只需包含您在此处发布的代码,或指向此 SO 问题的链接。)

我将尝试解释这里发生了什么。 (这听起来很熟悉,因为您已经在 GitHub 上报告了类似的问题;为了 SO 访问者,我在此重复解释。)让我们先看看您对 SetupSet 的调用:

sut.SetupSet(x => x.Antlers = true);

Moq 在其设置和验证方法中大量使用 LINQ 表达式树(Expression&lt;Action&lt;TMock,…&gt;&gt;Expression&lt;Func&lt;TMock,…&gt;&gt;)。表达式只是“代码即数据”,Moq 可以对其进行分析,以确定您希望您的模拟做什么(在设置期间),或者您的模拟应该发生什么(在验证期间)。

但是,由于 C# 编译器的限制(即它不能转换包含对表达式树的赋值的 lambda),Moq 的 SetupSet 不能使用表达式树;相反,它接受一个普通的Action&lt;TMock&gt;,即不能直接分析的一段代码。然而,起订量需要根据这段代码执行设置。在这种情况下,Moq 会以类似记录器的“试运行”模式(内部称为 FluentMockContext)调用此 lambda。然后,它会观察该“试运行”造成的影响,并以此为基础进行设置操作。

现在我们要谈到@Kritner 在comment above 中提到的一点:

SetupSet 通过构造函数,您的其他设置/验证都没有。

调用委托意味着它必须实际实例化模拟对象,以便它可以将它作为参数传递给您的设置 lambda。这意味着您的模拟类型的构造函数将运行。由于您已指定 CallBase = true,因此在该试运行期间,Moq 将调用您的 ExecuteMe 基本实现。这就是为什么我们最终采用你的方法抛出。

这里的错误是CallBase 并不能真正使用“试运行”原则,因为CallBase 的全部目的是在模拟类型中执行用户代码,这超出了起订量,因此不知道(当然也不应该知道)它应该在“试运行”模式下执行。

整个“试运行”模拟模式适用于许多常见的使用场景,但存在根本缺陷。我已经确定了quite a few problems with Moq that are caused by it,并且我一直在寻找方法(method decompilation 等)来替换用于它的内部组件(FluentMockContext)。

请将此作为错误提交到 Moq 的 GitHub 存储库,我会将其添加到问题列表中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-20
    • 2012-10-23
    • 2015-02-14
    • 2015-06-01
    • 2011-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多