调用模拟方法时是否可以使用 Moq 调用异步回调?
Is it possible to call an async callback with Moq when a mocked method is called?
我正在使用 Moq 模拟一些实现,我想验证在此接口上是否正确调用了一个方法,问题是它看起来像这样:
public interface IToBeMocked {
void DoThing(IParameter parameter);
}
public interface IParameter {
Task<string> Content { get; }
}
所以我设置了模拟:
var parameter = "ABC";
var mock = new Mock<IToBeMocked>();
mock
.Setup(m => m.DoThing(It.IsAny<IParameter>()))
.Callback<IParameter>(p async => (await p.Content).Should().Be(parameter));
new Processor(mock.Object).Process(parameter);
mock
.Verify(m => m.DoThing(It.IsAny<IParameter>()), Times.Once);
不幸的是,此测试已通过以下实施:
public class Processor {
public Processor(IToBeMocked toBeMocked){
_toBeMocked = toBeMocked;
}
public void Process(string parameter){
_toBeMocked.DoThing(null);
}
}
因为回调是异步的,但签名需要一个动作,这意味着永远不会等待等待者,并且测试在抛出异常之前结束。
Moq 中是否有等待异步回调的功能?
编辑
似乎有些混乱。我希望这能澄清问题。
我正在做 TDD。我已经实现了最简单的 shell 代码来编译测试。然后我编写了测试以确保 "ABC" 是 Task
的结果并且我设置了测试 运行。它正在过去。 这就是问题。我希望测试失败,所以我可以执行 'real' 实现。
编辑 2
我越想越觉得这可能是不可能的。我已经为回购打开了一个问题:https://github.com/moq/moq4/issues/737 但我在编写请求以预期提交 PR 时正在考虑实现这样一个功能,这似乎是不可能的。不过,如果有人有任何想法,我很乐意在这里或 GitHub 问题中听到他们的意见,我会及时更新这两个地方。现在,我想我将不得不使用存根。
回调需要一个动作,您尝试在所述回调中执行异步操作,归结为 async void
调用。在这种情况下无法捕获异常,因为它们是一劳永逸的。
引用Async/Await - Best Practices in Asynchronous Programming
所以问题不在于 Moq 框架,而在于所采用的方法。
使用回调获取所需的参数并从那里开始工作。
查看以下测试的进度,了解 TDD 方法如何随着每个测试的发展而发展。
[TestClass]
public class MyTestClass {
[Test]
public void _DoThing_Should_Be_Invoked() {
//Arrange
var parameter = "ABC";
var mock = new Mock<IToBeMocked>();
mock
.Setup(m => m.DoThing(It.IsAny<IParameter>()));
//Act
new Processor(mock.Object).Process(parameter);
//Assert
mock.Verify(m => m.DoThing(It.IsAny<IParameter>()), Times.Once);
}
[Test]
public void _Parameter_Should_Not_Be_Null() {
//Arrange
IParameter actual = null;
var parameter = "ABC";
var mock = new Mock<IToBeMocked>();
mock
.Setup(m => m.DoThing(It.IsAny<IParameter>()))
.Callback<IParameter>(p => actual = p);
//Act
new Processor(mock.Object).Process(parameter);
//Assert
actual.Should().NotBeNull();
mock.Verify(m => m.DoThing(It.IsAny<IParameter>()), Times.Once);
}
[Test]
public async Task _Parameter_Content_Should_Be_Expected() {
//Arrange
IParameter parameter = null;
var expected = "ABC";
var mock = new Mock<IToBeMocked>();
mock
.Setup(m => m.DoThing(It.IsAny<IParameter>()))
.Callback<IParameter>(p => parameter = p);
new Processor(mock.Object).Process(expected);
parameter.Should().NotBeNull();
//Act
var actual = await parameter.Content;
//Assert
actual.Should().Be(expected);
mock.Verify(m => m.DoThing(It.IsAny<IParameter>()), Times.Once);
}
}
如果您在回调上使用异步方法签名,它可能会或可能不会实际工作。取决于您的 运行time 决定继续使用同一线程还是启动一个新线程。
如果在回调中做一些断言或获取一些参数以供以后验证是有意义的,而且它经常这样做,你应该一起删除异步的东西并通过
强制它到运行
Task.Run(() => AsyncMethodHere).Result
我正在使用 Moq 模拟一些实现,我想验证在此接口上是否正确调用了一个方法,问题是它看起来像这样:
public interface IToBeMocked {
void DoThing(IParameter parameter);
}
public interface IParameter {
Task<string> Content { get; }
}
所以我设置了模拟:
var parameter = "ABC";
var mock = new Mock<IToBeMocked>();
mock
.Setup(m => m.DoThing(It.IsAny<IParameter>()))
.Callback<IParameter>(p async => (await p.Content).Should().Be(parameter));
new Processor(mock.Object).Process(parameter);
mock
.Verify(m => m.DoThing(It.IsAny<IParameter>()), Times.Once);
不幸的是,此测试已通过以下实施:
public class Processor {
public Processor(IToBeMocked toBeMocked){
_toBeMocked = toBeMocked;
}
public void Process(string parameter){
_toBeMocked.DoThing(null);
}
}
因为回调是异步的,但签名需要一个动作,这意味着永远不会等待等待者,并且测试在抛出异常之前结束。
Moq 中是否有等待异步回调的功能?
编辑
似乎有些混乱。我希望这能澄清问题。
我正在做 TDD。我已经实现了最简单的 shell 代码来编译测试。然后我编写了测试以确保 "ABC" 是 Task
的结果并且我设置了测试 运行。它正在过去。 这就是问题。我希望测试失败,所以我可以执行 'real' 实现。
编辑 2
我越想越觉得这可能是不可能的。我已经为回购打开了一个问题:https://github.com/moq/moq4/issues/737 但我在编写请求以预期提交 PR 时正在考虑实现这样一个功能,这似乎是不可能的。不过,如果有人有任何想法,我很乐意在这里或 GitHub 问题中听到他们的意见,我会及时更新这两个地方。现在,我想我将不得不使用存根。
回调需要一个动作,您尝试在所述回调中执行异步操作,归结为 async void
调用。在这种情况下无法捕获异常,因为它们是一劳永逸的。
引用Async/Await - Best Practices in Asynchronous Programming
所以问题不在于 Moq 框架,而在于所采用的方法。
使用回调获取所需的参数并从那里开始工作。
查看以下测试的进度,了解 TDD 方法如何随着每个测试的发展而发展。
[TestClass]
public class MyTestClass {
[Test]
public void _DoThing_Should_Be_Invoked() {
//Arrange
var parameter = "ABC";
var mock = new Mock<IToBeMocked>();
mock
.Setup(m => m.DoThing(It.IsAny<IParameter>()));
//Act
new Processor(mock.Object).Process(parameter);
//Assert
mock.Verify(m => m.DoThing(It.IsAny<IParameter>()), Times.Once);
}
[Test]
public void _Parameter_Should_Not_Be_Null() {
//Arrange
IParameter actual = null;
var parameter = "ABC";
var mock = new Mock<IToBeMocked>();
mock
.Setup(m => m.DoThing(It.IsAny<IParameter>()))
.Callback<IParameter>(p => actual = p);
//Act
new Processor(mock.Object).Process(parameter);
//Assert
actual.Should().NotBeNull();
mock.Verify(m => m.DoThing(It.IsAny<IParameter>()), Times.Once);
}
[Test]
public async Task _Parameter_Content_Should_Be_Expected() {
//Arrange
IParameter parameter = null;
var expected = "ABC";
var mock = new Mock<IToBeMocked>();
mock
.Setup(m => m.DoThing(It.IsAny<IParameter>()))
.Callback<IParameter>(p => parameter = p);
new Processor(mock.Object).Process(expected);
parameter.Should().NotBeNull();
//Act
var actual = await parameter.Content;
//Assert
actual.Should().Be(expected);
mock.Verify(m => m.DoThing(It.IsAny<IParameter>()), Times.Once);
}
}
如果您在回调上使用异步方法签名,它可能会或可能不会实际工作。取决于您的 运行time 决定继续使用同一线程还是启动一个新线程。 如果在回调中做一些断言或获取一些参数以供以后验证是有意义的,而且它经常这样做,你应该一起删除异步的东西并通过
强制它到运行Task.Run(() => AsyncMethodHere).Result