【问题标题】:Why in the world is this Moq + NUnit test failing?为什么这个 Moq + NUnit 测试失败了?
【发布时间】:2010-04-15 16:51:08
【问题描述】:

我有这个 dataAccess 模拟对象,我正在尝试验证它的方法之一正在被调用,并且传递给这个方法的参数满足某些约束。据我所知,这个方法确实被调用了,并且满足了约束。这行测试抛出了一个 MockException:

data.Verify(d => d.InsertInvoice(It.Is<Invoice>(i => i.TermPaymentAmount == 0m)), Times.Once());

但是,移除约束并接受任何发票通过了测试:

data.Verify(d => d.InsertInvoice(It.IsAny<Invoice>()), Times.Once());

我创建了一个测试窗口窗体,它实例化这个测试类,运行它的.Setup() 方法,然后调用我希望测试的方法。我在模拟对象未通过测试的代码行上插入断点

data.InsertInvoice(invoice);

实际将鼠标悬停在发票上,我可以确认其.TermPaymentAmount 十进制属性在调用该方法时确实为零。

出于绝望,我什至在我的 dataAccess 模拟中添加了一个回调:

data.Setup(d => d.InsertInvoice(It.IsAny<Invoice>())).Callback((Invoice inv) => MessageBox.Show(inv.TermPaymentAmount.ToString("G17")));

这给了我一个显示0 的消息框。这真的让我很困惑,我店里的其他人都无法弄清楚这一点。任何帮助将不胜感激。

一个几乎不相关的问题,我可能应该独立问,是使用Mock.Verify() 还是使用Mock.Expect() 更可取。可验证后跟Mock.VerifyAll(),正如我看到其他人所做的那样?如果答案是情境性的,哪些情况会值得使用其中一种?

【问题讨论】:

  • 你用的是什么版本的起订量?
  • 马克 - 你发现我在运行 4.0 测试版时没有意识到,尽管我已经切换到 3.1.0.0 并且仍然遇到同样的问题。
  • 我可以补充一点,我绝望地尝试了这个: data.Setup(d => d.InsertInvoice(It.Is(i => i.TermPaymentAmount != 0))) .Throws(new ArgumentException("TermPaymentAmount 不是 0!"));并且它的行为符合预期,并且不会抛出此异常,因为 Invoice.TermPaymentAmount 为 == 0。(当我将比较更改为 != 0 时它会抛出异常。)所以这确实完成了相同的检查我最初是在努力,虽然我不知道为什么我的初始方法不起作用。
  • 您对此有什么了解吗?
  • 这个特殊问题对我来说仍然是个谜,尽管我最近发现,当我验证一个模拟上的方法被调用“Times.Once()”时,Moq 给我的异常消息不清楚该方法是被调用了零次还是多次调用,所以我一直打算在有时间的时候重新解决这个问题。

标签: c# nunit moq


【解决方案1】:

只是为了排除这种情况(因为我有一些像你这样的奇怪行为:
尝试 Times.Exactly(1) 而不是 Times.Once()。 使用 Times.Once() 时发生了一些奇怪的事情 不过,我会猜到 Once() 在内部使用 Exactly(1) ...

【讨论】:

  • 这是一个很好的建议,但在这种情况下它并没有解决问题。不过谢谢!
【解决方案2】:

我正在使用 3.1.0.0 版的 Moq,并且在以下测试用例中没有遇到此问题。

internal class Program
{
    private static void Main(string[] args)
    {
        var mock = new Mock<IData>();
        mock.Setup(d => d.InsertInvoice(It.IsAny<Invoice>()));

        var service = new MyService(mock.Object);
        service.DoSomething(new Invoice());

        mock.Verify(d => d.InsertInvoice(It.Is<Invoice>(i => i.TermPaymentAmount == 0m)), Times.Once());

        Console.ReadLine();
    }
}

internal class MyService
{
    private readonly IData _data;

    public MyService(IData data)
    {
        _data = data;
    }

    public void DoSomething(Invoice invoice)
    {
        _data.InsertInvoice(invoice);
    }
}

public class Invoice
{
    public decimal TermPaymentAmount { get; set; }
}

public interface IData
{
    void InsertInvoice(Invoice invoice);
}

您能否提供更多关于您如何使用模拟的信息?

【讨论】:

  • 我使用模拟的方式几乎和你一样,马克。感谢您花时间整理这些。我只看到一些无关紧要的细微差别: 1. 我没有像你那样调用 mock.Setup() - 我使用的是松散的模拟行为,只是试图验证 .InsertInvoice() 方法是调用。我确实调用了 mock.Setup() 来添加那个绝望的回调,但那是在这个问题出现之后。 2. 还有一些其他细微的差别(发票对象是在我的代码前面创建的),虽然我还没有发现任何应该产生任何差别的东西。
  • 您收到的异常消息是什么?是否类似于 ""\r\n在模拟上的预期调用一次,但为 0 次:d => d.InsertInvoice(It.Is(i => (i.TermPaymentAmount = 0)))"" 或是别的吗?
【解决方案3】:

我的猜测是,它可能与浮点值等价检查所固有的问题有关。您的Invoice.TermPaymentAmount 属性值可能是计算结果吗?

要了解我在说什么,请参阅这个可能相关的问题: Is it safe to check floating point values for equality to 0?

【讨论】:

  • 这是一个很好的建议,许多同事也向我推荐。 Invoice.TermPaymentAmount 是一个小数,它是计算的结果,尽管在我的测试中它是一堆其他零值相互相加的结果,当我在我的 IDE 中实际运行测试时,当我添加MessageBox.Show 值的回调方法,它看起来都只是 0,没有任何小数点。我已经尝试测试该值是否等于 0,是否等于 0m,是否使用了 Decimal.Equals 等,但我无法通过此测试。
  • 我也尝试过测试 Math.Round(invoice.TermPaymentAmount,4) == 0,但这不起作用。感觉好像模拟库会坚持这个测试(特别是 mock.Verify() 调用)无论如何都会失败。当我找出答案时,我一定会更新这个问题!谢谢!
猜你喜欢
  • 1970-01-01
  • 2014-08-14
  • 2021-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-25
  • 1970-01-01
相关资源
最近更新 更多