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

时间:2018-12-21 21:28:40

标签: c# tdd moq

我正在模拟使用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);
    }
}

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

Moq中是否有任何功能可以等待异步回调?

编辑

似乎有些混乱。我希望这可以解决问题。

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

编辑2

我对这个问题的思考越多,我就越可能认为这是不可能的。我已经为回购打开了一个问题:https://github.com/moq/moq4/issues/737,但是我正在考虑实现这样的功能,因为我在准备提交PR时编写请求,这似乎是不可能的。仍然,如果有人有任何想法,我还是希望在这里或在GitHub问题中听到它们,我将使这两个地方保持最新状态。现在,我想我将不得不使用存根。

2 个答案:

答案 0 :(得分:1)

回调需要一个Action,您尝试在所述回调中执行异步操作,最终归结为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);
    }
}

答案 1 :(得分:0)

如果您在回调中使用异步方法签名,它可能会或可能不会实际工作。取决于您的运行时是决定继续使用同一线程还是启动一个新线程。 如果在回调中执行一些断言或获取一些参数以供稍后在回调中进行验证是有意义的,并且经常这样做,那么您应该一起删除异步内容并强制它通过

运行

Task.Run(() => AsyncMethodHere).Result