Moq中的SetupSequence

Flo*_*ian 54 c# unit-testing moq mocking

我想模拟第一次返回0,然后在调用方法的任何时候返回1.问题是,如果方法被调用4次,我应该写:

mock.SetupSequence(x => x.GetNumber())
    .Returns(0)
    .Returns(1)
    .Returns(1)
    .Returns(1);
Run Code Online (Sandbox Code Playgroud)

否则该方法返回null.

有没有办法写下次在第一次调用该方法后,该方法返回1?谢谢

为SetupSequence设置更多"运营商"是否合适?如果您认为是,您可以投票:http: //moq.uservoice.com/forums/11304-general/suggestions/2973521-setupsequence-more-operators

Rom*_*ier 42

这不是特别的花哨,但我认为它会起作用:

    var firstTime = true;

    mock.Setup(x => x.GetNumber())
        .Returns(()=>
                        {
                            if(!firstTime)
                                return 1;

                            firstTime = false;
                            return 0;
                        });
Run Code Online (Sandbox Code Playgroud)

  • 我认为这不是线程安全的,因此,如果您的测试涉及从不同线程调用“ GetNumber”的代码-您可能会多次返回“ 1”。为了实现线程安全,可以将Interlocked类用于对firstTime的读/写。 (3认同)

Jak*_*cki 39

最干净的方法是创建一个Queue和传递.Dequeue方法Returns

.Returns(new Queue<int>(new[] { 0, 1, 1, 1 }).Dequeue);

  • @JakubKonecki是的,确切地说.我只是警告其他人准确地遵循这种模式,并且不要像我那样意外地出现Dequeue(). (6认同)
  • 全部 - 避免我最初的错误.如果你定义它使得你有返回(myQueue.Dequeue())那么你只会得到第一个结果 - 因为你实际上已经将结果出列,而不是提供lambda表达式. (4认同)
  • @RomainVerdier - 不,事实并非如此。我认为 OP 要求提供 4 次调用的解决方案。 (3认同)
  • @ben我喜欢在测试结束时将异常抛出Moq语句(Verify的)放在Assert块(Arrange,Act,Assert)中,您可以在其中干净地检查某些内容是否被调用了确切的次数 (3认同)
  • 我不明白...这只会有效4次,之后就不会了,不是吗?这不是OP所要求的 (3认同)
  • @sfuqua - 这就是为什么我的答案使用委托而不是调用。 (2认同)

小智 12

通常情况下,我不会费心为这样一个老问题提交新答案,但近年来这种情况ReturnsAsync变得非常普遍,这使得潜在的答案变得更加复杂。

正如其他人所说,您基本上可以创建一个结果队列,并在您的Returns调用中传递queue.Dequeue委托,例如:

var queue = new Queue<int>(new[ ]{0,1,2,3});
mock.SetupSequence(m => m.Bar()).Returns(queue.Dequeue);
Run Code Online (Sandbox Code Playgroud)

但是,如果您要设置异步方法,我们通常应该调用ReturnsAsync. queue.Dequeue当传入时ReturnsAsync将导致对正在设置的方法的第一次调用正常工作,但随后的调用会抛出NullReferenceException. 您可以像其他一些示例一样创建自己的扩展方法来返回任务,但是这种方法不适用于SetupSequence,并且必须使用Returns来代替ReturnsAsync。此外,必须创建一个扩展方法来处理返回结果,这违背了使用 Moq 的初衷。在任何情况下,任何返回类型为 Task 的方法(您已将委托传递给该方法)ReturnsReturnsAsync在通过 设置时在第二次调用时始终会失败SetupSequence

然而,这种方法有两种有趣的替代方法,只需要最少的额外代码。第一个选项是认识到模拟对象SetupSetupAsync方法遵循 Fluent API 设计模式。这意味着,从技术上讲,SetupSetupAsyncReturnsReturnsAsync实际上都返回一个“Builder”对象。我所说的 Builder 类型对象是流利的 api 样式对象,例如QueryBuilder、和/ 。这样做的实际结果是我们可以轻松地做到这一点:StringBuilderModelBuilderIServiceCollectionIServiceProvider

var queue = new List<int>(){0,1,2,3};
var setup = mock.SetupSequence(m => m.BarAsync());
foreach(var item in queue)
{
  setup.ReturnsAsync(item);
}
Run Code Online (Sandbox Code Playgroud)

这种方法允许我们同时使用SetupSequenceReturnsAsync,在我看来,它遵循更直观的设计模式。

第二种方法是认识到Returns能够接受返回 a 的委托Task,并且Setup总是返回相同的东西。这意味着如果我们要创建一个Queue<T>像这样的扩展方法:

public static class EMs
{
  public static async Task<T> DequeueAsync<T>(this Queue<T> queue)
  {
    return queue.Dequeue();
  }
}
Run Code Online (Sandbox Code Playgroud)

那么我们可以简单地写:

var queue = new Queue<int>(new[] {0,1,2,3});
mock.Setup(m => m.BarAsync()).Returns(queue.DequeueAsync);
Run Code Online (Sandbox Code Playgroud)

或者可以使用AsyncQueuefrom 的类Microsoft.VisualStudio.Threading,这将允许我们这样做:

var queue = new AsyncQueue<int>(new[] {0,1,2,3});
mock.Setup(m => m.BarAsync()).Returns(queue.DequeueAsync);
Run Code Online (Sandbox Code Playgroud)

导致这一切的主要问题是,当到达设置序列末尾时,该方法被视为未设置。Setup为了避免这种情况,如果在到达序列末尾后返回结果,您还应该调用标准。

我整理了一个关于此功能的相当全面的小提琴,其中包含您在做错事情时可能遇到的错误的示例,以及正确做事的几种不同方法的示例。

https://dotnetfiddle.net/KbJlxb


Con*_*Low 8

聚会有点晚了,但如果您仍想使用 Moq 的 API,您可以Setup在最终Returns调用的操作中调用该函数:

var mock = new Mock<IFoo>();
mock.SetupSequence(m => m.GetNumber())
    .Returns(4)
    .Returns(() =>
    {
        // Subsequent Setup or SetupSequence calls "overwrite" their predecessors: 
        // you'll get 1 from here on out.
        mock.Setup(m => m.GetNumber()).Returns(1);
        return 1;
    });

var o = mock.Object;
Assert.Equal(4, o.GetNumber());
Assert.Equal(1, o.GetNumber());
Assert.Equal(1, o.GetNumber());
// etc...
Run Code Online (Sandbox Code Playgroud)

我想演示 using StepSequence,但是对于 OP 的特定情况,您可以简化并在Setup方法中包含所有内容:

mock.Setup(m => m.GetNumber())
    .Returns(() =>
    {
        mock.Setup(m => m.GetNumber()).Returns(1);
        return 4;
    });
Run Code Online (Sandbox Code Playgroud)

xunit@2.4.1Moq@4.14.1在这里测试了所有东西- 通过了吗?

  • 问题不是关于异步开发 (3认同)
  • 这似乎不适用于“ReturnsAsync”。方法内不能有 lamda 表达式作为参数。也许它适用于非异步开发,但现在谁这样做呢? (2认同)

slo*_*oth 6

您可以使用临时变量来跟踪该方法被调用的次数。

例子:

public interface ITest
{ Int32 GetNumber(); }

static class Program
{
    static void Main()
    {
        var a = new Mock<ITest>();

        var f = 0;
        a.Setup(x => x.GetNumber()).Returns(() => f++ == 0 ? 0 : 1);

        Debug.Assert(a.Object.GetNumber() == 0);
        for (var i = 0; i<100; i++)
            Debug.Assert(a.Object.GetNumber() == 1);
    }
}
Run Code Online (Sandbox Code Playgroud)


Tho*_*ink 5

只需设置一个扩展方法,如:

public static T Denqueue<T>(this Queue<T> queue)
{
    var item = queue.Dequeue();
    queue.Enqueue(item);
    return item;
}
Run Code Online (Sandbox Code Playgroud)

然后设置返回,如:

var queue = new Queue<int>(new []{0, 1, 1, 1});
mock.Setup(m => m.GetNumber).Returns(queue.Denqueue);
Run Code Online (Sandbox Code Playgroud)