【问题标题】:Setup on Mock not returning expected value模拟设置不返回预期值
【发布时间】:2016-04-15 10:30:35
【问题描述】:

这是我遇到的一个问题的简化版本:

public interface IService
{
    IProvider Provider { get; }
}

public interface IProvider
{
    List<int> Numbers{ get; }
    string Text { get; }
} 

[TestMethod]
public void ServiceTest()
{
    var service = new Mock<IService>();
    var provider = new Mock<IProvider>();

    service.Setup(s => s.Provider).Returns(provider.Object);    // A
    service.Setup(s => s.Provider.Text).Returns("some text");   // B - incorrect

    // they actually meant to do this, instead of 'B'
    // provider.Setup(p => p.Text).Returns("some text"); 

    provider.Setup(p => p.Numbers).Returns(new List<int> { 1, 2, 3 });

    DummyApplicationCode(service.Object);
}

int DummyApplicationCode(IService service)
{
    // will throw, because the Provider was replaced at 'B'
    int shouldBeOne = service.Provider.Numbers.First(); 
    return shouldBeOne;
}

单元测试失败,因为在被测试的应用程序代码中,被模拟的IService 返回了错误的IProvider

我最终发现了导致它的行(请记住我正在查看的代码不像上面那么简单),上面标记为“B”,这是其他人添加的原因误解了起订量设置。

我知道模拟上的后续设置将覆盖以前的设置,但我没有发现这个问题,因为违规行的返回是针对单独的子属性。

我认为这是设计使然,但我没想到有人会这样做。

我的问题:既然'B'的设置只关心提供者Text的返回,为什么服务'Provider'属性需要替换它是在“A”处定义的?

【问题讨论】:

  • If more than one setup is specified for the same method or property, the latest one wins and is the one that will be executed.,你已经知道了。 Provider 是您的案例中正在执行的表达式树的一部分。因此,任何涉及该路径的先前设置也将被覆盖。
  • @Nkosi 这很公平,谢谢,但我的问题是为什么它真的 需要 这样做,而不是将 Text 返回到已经存在的 Provider 模拟上。就个人而言,我希望看到该行被标记为错误。
  • 我明白你的意思。我的猜测是每个Setup 都被单独处理。要调用的不同操作/功能。您可以在 GitHub 上为其创建功能请求问题。

标签: c# unit-testing moq


【解决方案1】:

这在查看源代码时显然是故意的:

https://github.com/moq/moq4/blob/master/Source/Mock.cs

https://github.com/moq/moq4/blob/master/Source/Interceptor.cs

Setup 通过在Interceptor 上使用AddCall 创建一个“呼叫”。这包含以下代码块,只要我们创建一个非条件设置,它就会删除所有以前的设置。它甚至被评论了。

if (!call.IsConditional)
            {
                lock (calls)
                {
                    // if it's not a conditional call, we do
                    // all the override setups.
                    // TODO maybe add the conditionals to other
                    // record like calls to be user friendly and display
                    // somethig like: non of this calls were performed.
                    if (calls.ContainsKey(key))
                    {
                        // Remove previous from ordered calls
                        InterceptionContext.RemoveOrderedCall(calls[key]);
                    }

                    calls[key] = call;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    • 2018-08-17
    • 1970-01-01
    • 2016-11-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多