您能帮我了解Moq回调吗?


95

使用Moq进行了研究,Callback但我无法找到一个简单的示例来了解如何使用它。

您是否有一个小的工作片段,清楚地说明了如何以及何时使用它?

Answers:


83

很难击败https://github.com/Moq/moq4/wiki/Quickstart

如果还不够清楚,我会称其为文档错误...

编辑:为响应您的澄清...

对于Setup执行的每个模拟方法,您都需要指出以下内容:

  • 输入约束
  • 用于/返回值(如果有)的方法的值

.Callback机制说:“我现在无法描述,但是当发生这样的呼叫时,请给我回电,然后我将做需要做的事情”。作为同一条流畅的调用链的一部分,您可以控制结果通过.Returns“” 返回(如果有的话)。在QS示例中,一个示例是使返回值每次都增加。

通常,您将不需要这种机制(xUnit测试模式具有测试中类似的条件逻辑的反模式术语),并且如果有任何更简单或内置的方式来建立所需的东西,应该优先使用。

Justin Etheredge的Moq系列的4的第3部分对此进行了介绍,这里还有另一个回调示例

一个简单的回调示例可以在使用Moq回调中找到。


3
嗨,鲁本(Ruben),我正在学习Moq,如果您愿意,我正在举很多例子,以了解如何使用Moq做事。我的问题是我不知道何时使用它。一旦我知道解决了这个问题,我将编写自己的代码。如果您用自己的话来解释它,那么何时使用回调函数?感谢您的
宝贵

15
很难击败[链接]?一点也不。该链接向您展示了如何做许多不同的事情,但是并没有告诉您为什么需要做任何一件事情。我发现,这是模拟文档中的一个常见问题。我可以用零的手指指望我发现的TDD +模拟的好而清晰的解释。大多数人假设他们具有一定的知识水平,如果我知道的话,则无需阅读本文。
Ryan Lundy

@Kyralessa:我明白你的意思。我个人有相当多的书籍知识,所以发现快速入门的东西绝对是完美的。不幸的是,我不知道我在文章末尾链接到的一个更好的例子。如果您找到一个,请将其张贴在此处,我将很乐意对其进行编辑(或随时进行DIY)
Ruben Bartelink 2011年

“我将做需要做的事情,并告诉您要返回的结果(如果有)”,我认为这具有误导性,AFAIU Callback与返回值无关(除非您碰巧通过代码将其链接)。基本上,它仅确保在每次调用之前或之后(取决于是否Returns分别在其之前或之后将其链接)都以简单和简单的方式调用回调。
Ohad Schneider

1
@OhadSchneider关注我的链接...你是正确的!想知道Fluent界面是否已更改(似乎不太可能,即我做出了错误的假设,并且没有像正常工作一样阅读与之链接的内容)(但不是真的很感兴趣,因为没有使用Moq太久了)来自我自己的自动补全)。希望修复解决您的问题,让我知道是否可以解决
Ruben Bartelink

59

这是一个使用回调测试发送到处理插入的数据服务的实体的示例。

var mock = new Mock<IDataService>();
DataEntity insertedEntity = null;

mock.Setup(x => x.Insert(It.IsAny<DataEntity>())).Returns(1) 
           .Callback((DataEntity de) => insertedEntity = de);

替代的通用方法语法:

mock.Setup(x => x.Insert(It.IsAny<DataEntity>())).Returns(1) 
           .Callback<DataEntity>(de => insertedEntity = de);

然后您可以测试类似

Assert.AreEqual("test", insertedEntity.Description, "Wrong Description");

4
可以说,对于这种特定情况(取决于您是要表达针对状态还是行为的测试),在某些情况下使用It.Is<T>in Mock.Verify代替使用临时的测试可能会更干净。但是+1是因为我敢打赌,从一个例子来看,有很多人会做得最好。
Ruben Bartelink

10

Callback最小起订量有两种类型。一种情况是在呼叫返回之前发生;另一个发生在通话返回后。

var message = "";
mock.Setup(foo => foo.Execute(arg1: "ping", arg2: "pong"))
    .Callback((x, y) =>
    {
        message = "Rally on!";
        Console.WriteLine($"args before returns {x} {y}");
    })
    .Returns(message) // Rally on!
    .Callback((x, y) =>
    {
        message = "Rally over!";
        Console.WriteLine("arg after returns {x} {y}");
    });

在这两个回调中,我们可以:

  1. 检查方法参数
  2. 捕获方法参数
  3. 改变情境状态

2
实际上,两者都在呼叫返回之前发生(就呼叫者而言)。请参阅stackoverflow.com/a/28727099/67824
Ohad Schneider

5

Callback只是在调用模拟方法之一时执行所需的任何自定义代码的一种方法。这是一个简单的例子:

public interface IFoo
{
    int Bar(bool b);
}

var mock = new Mock<IFoo>();

mock.Setup(mc => mc.Bar(It.IsAny<bool>()))
    .Callback<bool>(b => Console.WriteLine("Bar called with: " + b))
    .Returns(42);

var ret = mock.Object.Bar(true);
Console.WriteLine("Result: " + ret);

// output:
// Bar called with: True
// Result: 42

我最近遇到了一个有趣的用例。假设您希望对模拟进行一些调用,但是它们同时发生。因此,您无法知道呼叫的顺序,但是您想知道发生了预期的呼叫(与顺序无关)。您可以执行以下操作:

var cq = new ConcurrentQueue<bool>();
mock.Setup(f => f.Bar(It.IsAny<bool>())).Callback<bool>(cq.Enqueue);
Parallel.Invoke(() => mock.Object.Bar(true), () => mock.Object.Bar(false));
Console.WriteLine("Invocations: " + String.Join(", ", cq));

// output:
// Invocations: True, False

BTW不要被误导性的“之前Returns”和“之后Returns”区分所迷惑。这仅仅是您的自定义代码Returns在评估之后还是之前运行的技术区别。在调用者眼中,两者都将在返回值之前运行。确实,如果该方法是void-returning ,则您甚至都不能调用Returns它,但是它的作用相同。有关更多信息,请参见https://stackoverflow.com/a/28727099/67824


1

除了这里的其他好答案之外,我还使用它在引发异常之前执行逻辑。例如,我需要存储传递给方法的所有对象以供以后验证,并且该方法(在某些测试用例中)需要引发异常。调用.Throws(...)Mock.Setup(...)覆盖的Callback()行动,并永远不会调用它。但是,通过在Callback中引发异常,您仍然可以执行回调必须提供的所有功能,并且仍然可以引发异常。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.