使用SetupSet'忘记'方法设置

时间:2017-07-28 14:20:39

标签: c# unit-testing moq

当我尝试解决与起订量不同的情况下,我试图使用SetupSet来解决。这揭示了另一个潜在的问题。

当我在属性上使用SetupSet以及方法上的设置时,Moq似乎忘了'方法上的设置已经完成。

以下是示例代码,非常简单:

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的单元测试报告异常(&#34;为什么我在这里?&#34;)抛出方法ExecuteMe(),这证明即使有安装程序也执行ExecuteMe()方法( x =&gt; x.ExecuteMe())来防止它。其他两个单元测试通过(显然不执行ExecuteMe())。

我甚至尝试在ExecuteMe()的Setup上设置Call​​back,但结果相同。我也颠倒了Setup和SetupSet的顺序(在代码中),但没有用。

为什么SetupSet可能影响方法设置?

1 个答案:

答案 0 :(得分:1)

  

为什么SetupSet可能影响方法设置?

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

我会试着解释这里发生了什么。 (这听起来很熟悉,因为你已经在GitHub上报告了类似的问题;我在这里为了SO访问者重复这里的解释。)让我们首先看一下你对SetupSet的号召:

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

Moq在其设置和验证方法中大量使用LINQ表达式树(Expression<Action<TMock,…>>Expression<Func<TMock,…>>)。表达式只是“代码为数据”,Moq可以分析这些数据以确定您希望模拟执行的操作(在安装过程中),或模拟应该发生的事情(在验证期间)。

但是,由于C#编译器的限制(即它不能将包含赋值的lambda转换为表达式树),Moq的SetupSet不能使用表达式树;相反,它接受普通的Action<TMock>,即一段无法直接分析的代码。然而,Moq需要根据这段代码执行设置。在这种情况下发生的是Moq以类似记录器的“干运行”模式(内部称为FluentMockContext)调用此lambda。然后它观察“干运行”造成的影响,并将其设置动作建立在它们上面。

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

  

SetupSet遍历构造函数,其他任何设置/验证都没有。

调用委托意味着它必须实际实例化mock对象,以便它可以将它作为参数传递给你的setup lambda。这意味着您的模拟类型的构造函数将运行。而且因为你已经指定了CallBase = true,所以在那次干旱运行中,Moq会调用你的ExecuteMe基础实现。这就是为什么我们最终会抛出你的方法。

这里的错误是CallBase并不真正适用于“干运行”原则,因为CallBase的整个目的是在模拟类型中执行用户代码,这就是谎言在Moq的控制范围之外,因此不知道(并且正确地不应该知道)它应该以“干运行”模式执行。

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

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