2017-07-28 73 views
4

虽然我正在尝试解决与Moq不同的情况,但我试图使用SetupSet来解决。这揭开了另一个潜在的问题。使用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报告异常(单元测试)在ExecuteMe()方法中抛出,它证明即使在有Setup(x => x.ExecuteMe())的情况下,ExecuteMe()方法也会执行以防止它。另外两个单元测试通过(显然不执行ExecuteMe())。

我甚至试图把安装程序的ExecuteMe()回调,但结果相同。我也颠倒了Setup和SetupSet的顺序(在代码中),无济于事。

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

+2

它没有,我无法找到为什么文档,但'SetupSet'通过构造函数,其中没有其他设置/验证所做的。在构造函数中调用的虚方法会导致奇怪的行为,但是这看起来像'SetupSet'还有一个奇怪的地方 – Kritner

回答

1

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

我相信这是Moq中的一个错误。你可以这么和蔼,file an issue at Moq's GitHub repository moq/moq4? (只包括你在这里发布的代码,或链接到这个SO问题。)

我会尽力解释这里发生了什么。 (这听起来很熟悉了,因为你已经报道在GitHub上类似的问题,我在这里重复解释,让游客着想。)让我们首先看一下您的来电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。然后观察这种“空转”造成的影响,并根据它们的设置行动。

现在,我们正在通过@Kritner在comment above提到一个观点:

SetupSet经过构造,在那里您没有任何其他设置的/验证做。

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

这里的问题是,CallBase并不真正符合这个“空运行”原则,因为CallBase的整个目的是执行模拟类型中的用户代码,该代码位于Moq的控制之外,并且因此不知道(并且理所当然地不应该知道)它应该以“空转”模式执行。

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

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

+1

我把这个作为bug [在这里]发布(https://github.com/moq/moq4/issues/432 )。谢谢! –