你能帮我理解一下 Moq Callback 吗?

使用 Moq 并查看 Callback,但是我还没有找到一个简单的例子来理解如何使用它。

您是否有一个小的工作片段来清楚地解释如何以及何时使用它?

117358 次浏览

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

如果这还不够清楚,我会称之为医生臭虫。

编辑: 回应你的澄清..。

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

  • 对输入的限制
  • 返回值(如果有的话)的派生方式的值

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

一般来说,您不会经常需要这样的机制(xUnit 测试模式中有类似条件逻辑的反模式术语) ,如果有任何更简单或内置的方法来建立您需要的东西,应该优先使用它。

Justin Etheredge 的 Moq 系列的第三部分介绍了这个问题,这里还有一个回调的例子

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

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

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

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

在 Moq 有两种类型的 ABc0。一个发生在调用返回之前,另一个发生在调用返回之后。

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. 改变上下文状态

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

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

我最近遇到了一个有趣的用例。假设您期望调用您的 mock,但它们是同时发生的。所以你没有办法知道他们接到电话的顺序,但是你想知道你期望的电话确实发生了(不管顺序)。你可以这样做:

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

顺便说一句,不要被误导性的“ Returns之前”和“ Returns之后”的区别所迷惑。这仅仅是一个技术上的区别,即您的自定义代码是在 Returns被计算之后还是之前运行。在调用者看来,两者都会在返回值之前运行。事实上,如果方法是 void-返回,你甚至不能调用 Returns,但它的工作原理是一样的。有关详细信息,请参阅 https://stackoverflow.com/a/28727099/67824

我再举一个例子:

我需要测试的方法叫做 Add。它通过执行另一个方法将结果存储在数据库中,并返回 void

public class SystemUnderTest
{
private readonly Repository _repository;


public SystemUnderTest(Repository repository)
{
_repository = repository;
}


public void Add(int a, int b)
{
int result = a + b;
_repository.StoreResult(result);
}
}




public class Repository
{
public void StoreResult(int result)
{
// stores the result in the database
}
}

由于 Add的返回类型,我不能直接得到结果并断言它。我必须得到 StoreResult方法的输入。为此,我在模仿 Repository的方法时使用了回调。

using Moq;
using Xunit;


namespace TestLocal.Tests;


public class CallbackTest
{
private readonly SystemUnderTest _sut;
private readonly Mock<Repository> _repository;


public CallbackTest()
{
_repository = new Mock<Repository>(MockBehavior.Strict);
_sut = new SystemUnderTest(_repository.Object);


}


[Fact]
public void AddTest()
{
int a = 1;
int b = 2;


int result = -1;
_repository.Setup(x => x.StoreResult(3))
.Callback<int>(callbackResult => result = callbackResult)
.Verifiable();


_sut.Add(a,b);


Assert.Equal(a+b, result);
}
}