[英]SetupSequence in Moq
我想要一個第一次返回0
的模擬,然后在以后調用該方法時返回1
。 問題是如果該方法被調用 4 次,我必須寫:
mock.SetupSequence(x => x.GetNumber())
.Returns(0)
.Returns(1)
.Returns(1)
.Returns(1);
否則,該方法返回 null。
有沒有什么辦法可以寫出,在初始調用之后,該方法返回1
?
最干凈的方法是創建一個Queue
並將.Dequeue
方法傳遞給Returns
.Returns(new Queue<int>(new[] { 0, 1, 1, 1 }).Dequeue);
這不是特別花哨,但我認為它會起作用:
var firstTime = true;
mock.Setup(x => x.GetNumber())
.Returns(()=>
{
if(!firstTime)
return 1;
firstTime = false;
return 0;
});
聚會有點晚了,但如果您仍想使用 Moq 的 API,您可以在最終Returns
調用的操作中調用Setup
函數:
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...
我想使用StepSequence
進行演示,但對於 OP 的特定情況,您可以簡化並在Setup
方法中包含所有內容:
mock.Setup(m => m.GetNumber())
.Returns(() =>
{
mock.Setup(m => m.GetNumber()).Returns(1);
return 4;
});
使用xunit@2.4.1和Moq@4.14.1在這里測試了所有內容 - 通過 ✔
只需設置一個擴展方法,如:
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);
您可以使用臨時變量來跟蹤調用該方法的次數。
例子:
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);
}
}
通常情況下,我不會為這樣一個老問題提交新的答案,但近年來 ReturnsAsync 變得非常普遍,這使得潛在的答案變得更加復雜。
正如其他人所說,您基本上可以只創建一個結果隊列,並在您的 Returns 調用中傳遞 queue.Dequeue 委托。
例如。
var queue = new Queue<int>(new []{0,1,2,3});
mock.SetupSequence(m => m.Bar()).Returns(queue.Dequeue);
但是,如果您正在設置異步方法,我們通常應該調用 ReturnsAsync。 queue.Dequeue 傳遞到 ReturnsAsync 時,將導致對正在設置的方法的第一次調用正常工作,但隨后的調用將引發 Null 引用異常。 您可以像其他一些示例一樣創建您自己的返回任務的擴展方法,但是這種方法不適用於 SetupSequence,並且必須使用 Returns 而不是 ReturnsAsync。 此外,必須創建一個擴展方法來處理返回結果,這違背了使用 Moq 的初衷。 在任何情況下,如果您已將委托傳遞給 Returns 或 ReturnsAsync,任何返回類型為 Task 的方法在通過 SetupSequence 設置時在第二次調用時總是會失敗。
然而,有兩種有趣的替代方法可以替代這種方法,它們只需要最少的附加代碼。 第一個選項是識別 Mock 對象的 Setup 和 SetupAsync 遵循 Fluent Api 設計模式。 這意味着,從技術上講,Setup、SetupAsync、Returns 和 ReturnsAsync 實際上都返回一個“Builder”object。我所說的 Builder 類型 object 是流暢的 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 能夠接受返回 Task 的委托,並且 Setup 將始終返回相同的東西。 這意味着如果我們要像這樣為 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 class,這將允許我們這樣做:
var queue = new AsyncQueue<int>(new []{0,1,2,3});
mock.Setup(m => m.BarAsync()).Returns(queue.DequeueAsync);
導致所有這一切的主要問題是,當到達設置序列的末尾時,該方法被視為未設置。 為避免這種情況,如果在到達序列末尾后應返回結果,您還應調用標准設置。
我整理了一個關於此功能的相當全面的小提琴,其中包含您在做錯事時可能遇到的錯誤示例,以及您可以正確做事的幾種不同方式的示例。 https://do.netfiddle.net/KbJlxb
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.