Moq:设置一个模拟方法在第一次调用时失败,在第二次调用时成功

ant*_*ony 37 c# nunit unit-testing moq mocking

什么是最简洁的方式使用Moq来模拟一个方法,它会在第一次调用时抛出异常,然后在第二次调用它时成功?

rsb*_*rro 48

我会使用Callback并递增一个计数器来确定是否从中抛出异常Callback.

[Test]
public void TestMe()
{
    var count = 0;
    var mock = new Mock<IMyClass>();
    mock.Setup(a => a.MyMethod()).Callback(() =>
        {
            count++;
            if(count == 1)
                throw new ApplicationException();
        });
    Assert.Throws(typeof(ApplicationException), () => mock.Object.MyMethod());
    Assert.DoesNotThrow(() => mock.Object.MyMethod());
}

public interface IMyClass
{
    void MyMethod();
}
Run Code Online (Sandbox Code Playgroud)

  • 如果你想返回一个值序列,那么ReturnsInOrder()是很好的,但是如果使用它作为序列的一部分抛出异常则不太明显. (3认同)
  • 这个答案并没有错,但是使用 `SetupSequence` 肯定更容易、更优雅。检查@aghidini 的答案。 (3认同)

agh*_*ini 23

从Moq 4.2开始,您可以使用内置方法SetupSequence()(如@RichardBarnett评论所述).

例:

var mock = new Mock<IMyClass>();
mock.SetupSequence(x => x.MyMethod("param1"))
    .Throws<MyException>()
    .Returns("test return");
Run Code Online (Sandbox Code Playgroud)

  • 仅当“MyMethod”具有返回类型时才有效。如果该方法是“void”,那么您不能使用此方法。如果有一种方法可以使用“void”方法来做到这一点,那就太好了 (5认同)

ant*_*ony 16

到目前为止,我提出的最好的是:

interface IFoo
{
    void Bar();
}

[Test]
public void TestBarExceptionThenSuccess()
{
    var repository = new MockRepository(MockBehavior.Default);
    var mock = repository.Create<IFoo>();

    mock.Setup(m => m.Bar()).
        Callback(() => mock.Setup(m => m.Bar())). // Setup() replaces the initial one
        Throws<Exception>();                      // throw an exception the first time

    ...
}
Run Code Online (Sandbox Code Playgroud)

  • +1 这是一个很酷的方法。对于那些绊倒代码的人来说,可能不清楚那里发生了什么。 (2认同)
  • 请注意,替换模拟会消除历史记录,因此您无法执行`mock.Verify(m => m.Bar(),Times.Exactly(2))`. (2认同)

Mat*_*ias 5

Phil Haack有一篇关于设置返回特定结果序列的方法的有趣博客文章.它似乎是一个很好的起点,涉及一些工作,因为你需要现在有一系列结果可以是T类型或异常,而不是某种类型的值序列.