C#Moq VerifySet throws Expression不是非平凡setter的属性setter调用

时间:2017-07-27 22:40:04

标签: c# unit-testing moq

在C#中,即使使用SetupProperty或SetupSet,当setter非常重要时,Moq VerifySet也会抛出Expression is not a property setter invocation.

这是一个简单的例子。请注意,Antlers setter是微不足道的,而Antlers2 setter并不简单。:

public class Dancer
{

    public Dancer(bool pIsMale)
    {
        IsMale = pIsMale;
    }

    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 bool Antlers2
    {
        get { return this._Antlers; }
        set
        {
            // females cannot have antlers
            if (IsMale)
                this._Antlers = value;
            else
                this._Antlers = false;
        }
    }
}

以下是单元测试。第二组三个(使用Antlers2)在其他方面与第一组三个相同(使用Antlers)。使用Antler的所有单元测试都通过了测试。所有使用Antlers2的单元测试抛出Expression is not a property setter invocation.,即使使用SetupProperty,我认为整个属性的实现完全被忽略并被Moq取代。

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

        // create mock of class under test
        var sut = new Mock<Dancer>(true) { CallBase = true };

        // 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<Dancer>(true) { CallBase = true };
        sut.SetupProperty(x => x.Antlers, false);

        // 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<Dancer>(true) { CallBase = true };
        sut.SetupSet(x => x.Antlers = true);

        // Act
        sut.Object.Antlers = true;

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

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

        // create mock of class under test
        var sut = new Mock<Dancer>(true) { CallBase = true };

        // Act
        sut.Object.Antlers2 = true;

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

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

        // create mock of class under test
        var sut = new Mock<Dancer>(true) { CallBase = true };
        sut.SetupProperty(x => x.Antlers2, false);

        // Act
        sut.Object.Antlers2 = true;

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

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

        // create mock of class under test
        var sut = new Mock<Dancer>(true) { CallBase = true };
        sut.SetupSet(x => x.Antlers2 = true);

        // Act
        sut.Object.Antlers2 = true;

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

}

什么是关于混淆Moq的VerifySet的非平凡的基类属性设置器?我正在使用Moq 4.7.99,Visual Studio 2015,目标是.Net Framework 4.5.2。

感谢您的帮助!我从StackOverflow中受益匪浅!

1 个答案:

答案 0 :(得分:0)

所以看起来你有很多条件正在创造这个奇怪的场景。这可能是一个值reporting的错误,我不确定。无论如何,你头晕目眩似乎来自于设置Callbase = true和/或从IsMale属性设置器中调用Antler2

设置Callbase = true会为您班级中的每个虚拟方法创建一个MockInvocation - 包括IsMale(稍后这将很重要)。从表面上看,这看起来并不是什么大不了的事,但事实证明确实如此。在SetupSetImpl方法中,Moq会抛出异常。此方法验证 Moq调用的最后一个MockInvocation是否为setter - 它通过检查上一个调用方法的名称以'set_'开头来执行此操作。

使用普通的“普通的setter”,最后一次调用将是Antlers2的设置器,但是你引入了一个扭曲。在你的二传手中,你打电话给IsMale,因为你在创建模拟时也设置了Callbase = true,你的MockInvocation属性就有一个IsMale。那么当您设置Antler2属性时会发生什么''set_Antler2'被添加到调用列表中,然后在IsMale属性setter中调用Antler2时,'get_IsMale'被添加到调用列表。因此,当SetupSetImpl检查上次调用是否是一个setter时,它会找到一个getter和panics。

我已经测试并确认至少有两种方法可以防止此异常。首先是不设置Callbase = true。但这是一个问题,因为您需要模拟对象将Antler2属性设置器传递给基础对象 - 这是测试的整个点。另一个更简单的解决方案是完全绕过IsMale属性,只需直接从Antler2属性设置器调用支持字段,如下所示:

if (this._IsMale == true) // Notice I'm calling _IsMale
    this._Antlers2 = value;
else
    this._Antlers2 = false;

即使设置了Callbase = true,这仍然有效,因为您没有调用IsMale属性,因此不会将get_IsMale添加到调用列表中。相反,您只需检查IsMale属性使用的支持字段。