当调用模拟方法时,是否可以使用 Moq 调用异步回调?

Jam*_*lls 3 c# tdd moq

我正在使用 Moq 模拟一些实现,我想验证在此接口上是否正确调用了一个方法,问题是它看起来像这样:

public interface IToBeMocked {
    void DoThing(IParameter parameter);
}

public interface IParameter {
    Task<string> Content { get; }
}
Run Code Online (Sandbox Code Playgroud)

所以我设置了我的模拟:

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);
Run Code Online (Sandbox Code Playgroud)

不幸的是,该测试已经通过以下实现:

public class Processor {
    public Processor(IToBeMocked toBeMocked){
        _toBeMocked = toBeMocked;
    }

    public void Process(string parameter){
        _toBeMocked.DoThing(null);
    }
}
Run Code Online (Sandbox Code Playgroud)

因为回调是异步的,但签名需要一个操作,这意味着永远不会等待等待者,并且测试在引发异常之前结束。

Moq 中是否有等待异步回调的功能?

编辑

似乎有些混乱。我希望这能澄清问题。

我正在做TDD。我已经实现了最简单的代码外壳来编译测试。然后我编写了测试以确保“ABC”是结果,Task并且我已将测试设置为运行。正在过去。这就是问题所在。我希望测试失败,这样我就可以实现“真正的”实现。

编辑2

我越想越觉得这可能是不可能的。我已经为仓库提出了一个问题: https: //github.com/moq/moq4/issues/737,但我正在考虑实现这样一个功能,因为我正在编写请求以期待提交 PR,并且这似乎不可能。不过,如果有人有任何想法,我很乐意在这里或在 GitHub 问题中听到他们的想法,我会让这两个地方保持最新状态。现在,我想我将不得不使用存根。

Nko*_*osi 5

回调需要一个操作,您尝试在所述回调中执行异步操作,该操作归结为async void调用。在这种情况下无法捕获异常,因为它们是即发即忘的。

参考Async/Await - 异步编程的最佳实践

因此,问题不在于 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);
    }
}
Run Code Online (Sandbox Code Playgroud)