最小起订量中的设置顺序

问题描述 投票:0回答:7

我想要一个模拟,它第一次返回

0
,然后在此后调用该方法时返回
1
。问题是,如果该方法被调用4次,我必须这样写:

mock.SetupSequence(x => x.GetNumber())
    .Returns(0)
    .Returns(1)
    .Returns(1)
    .Returns(1);

否则,该方法返回 null。

有没有什么办法可以写成,在初次调用之后,该方法返回

1

c# unit-testing mocking moq
7个回答
79
投票

最简洁的方法是创建一个

Queue
并将
.Dequeue
方法传递给
Returns

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


65
投票

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

    var firstTime = true;

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

                            firstTime = false;
                            return 0;
                        });

30
投票

有点晚了,但如果您仍然想使用 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...

我想使用

SetupSequence
进行演示,但对于 OP 的具体情况,您可以简化并将所有内容都放在
Setup
方法中:

mock.Setup(m => m.GetNumber())
    .Returns(() =>
    {
        mock.Setup(m => m.GetNumber()).Returns(1);
        return 4;
    });

使用 [电子邮件受保护][电子邮件受保护]测试了此处的所有内容 - 通过 ✔


9
投票

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

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

例如。

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

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

然而,这种方法有两种有趣的替代方法,只需要最少的额外代码。第一个选项是识别 Mock 对象的 Setup 和 SetupAsync 遵循 Fluent Api 设计模式。这意味着,从技术上讲,Setup、SetupAsync、Returns 和 ReturnsAsync 实际上都返回一个“Builder”对象。我所说的 Builder 类型对象是流畅的 api 样式对象,例如 QueryBuilder、StringBuilder、ModelBuilder 和 IServiceCollection/IServiceProvider。这样做的实际结果是我们可以轻松做到这一点:

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

这种方法允许我们同时使用SetupSequence和ReturnsAsync,我认为这遵循更直观的设计模式。

第二种方法是认识到 Returns 能够接受返回任务的委托,并且安装程序将始终返回相同的内容。这意味着如果我们要为 Queue 创建一个扩展方法,如下所示:

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

然后我们可以简单地写:

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

或者可以使用 Microsoft.VisualStudio.Threading 中的 AsyncQueue 类,这将允许我们执行此操作:

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

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

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


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);
    }
}

5
投票

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

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

然后设置返回:

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

3
投票

Moq 使用 builder 模式,设置模拟的行为。 您可以,但不必使用流畅的界面。 记住这一点,我这样解决了这个问题:

var sequence = myMock
            .SetupSequence(""the method I want to set up"");

foreach (var item in stuffToReturn)
{    
  sequence = sequence.Returns(item);
}
© www.soinside.com 2019 - 2024. All rights reserved.