c# - SetupSet 'forgets'方法设置的使用

标签 c# unit-testing moq

当我尝试使用 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 的单元测试报告在方法 ExecuteMe() 中抛出异常(“我为什么在这里?”),这证明即使有 Setup(x => x.ExecuteMe) 方法 ExecuteMe() 也会执行()) 来阻止它。其他两个单元测试通过(显然不执行 ExecuteMe())。

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

有没有想过为什么 SetupSet 会影响方法 Setup?

最佳答案

Any thoughts why the SetupSet could have affected the method Setup?

我认为这是最小起订量中的错误。请你这么客气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 可以对其进行分析以确定您希望您的 mock 做什么(在设置期间),或者您的 mock 应该发生什么(在验证期间)。

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

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

SetupSet goes through the constructor, where none of your other setup/verifies do.

调用委托(delegate)意味着它必须实际实例化模拟对象,以便它可以将它作为参数传递给您的设置 lambda。这意味着您的模拟类型的构造函数将运行。因为你指定了 CallBase = true , 在那次试运行期间,最小起订量会调用你的 ExecuteMe基础实现。这就是为什么我们最终采用您抛出的方法。

这里的错误是 CallBase并不真正适用于“预演”原则,因为 CallBase 的全部目的是在模拟类型中执行用户代码,它位于 Moq 的控制之外,因此不知道(而且理应永远不必知道)它应该以“试运行”模式执行。

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

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

关于c# - SetupSet 'forgets'方法设置的使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45375841/

相关文章:

c# - Unity 访问非静态成员需要对象引用 C#

c# - 使用没有返回类型的 yield

unit-testing - 支持集成测试而不是单元测试是错误的吗?

reactjs - Kotlin JS 中的单元测试

ruby-on-rails - 如果我调用 Factory.build 以使我的 Controller 测试快速,我怎样才能让 Factory Girl 永远不会访问数据库?

c# - 无法初始化模拟类的成员

c# - 为什么在 Controller 上设置了 SessionStateBehavior.ReadOnly 后我仍然可以写入 session ,我应该关心吗?

c# - 在 Jquery 中设置选定值后,无法在 C# 上找到下拉选定值

linq - 最小起订量和回调以设置类的值