简体   繁体   English

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

[英]Is it possible to call an async callback with Moq when a mocked method is called?

I'm mocking some implementation using Moq and I want verify a method is called correctly on this interface, the problem is it looks something like this:我正在使用 Moq 模拟一些实现,我想验证在此接口上是否正确调用了一个方法,问题是它看起来像这样:

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

public interface IParameter {
    Task<string> Content { get; }
}

So I set up my mock:所以我设置了我的模拟:

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

Unfortunately, this test already passes with the following implementation:不幸的是,这个测试已经通过以下实现:

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

    public void Process(string parameter){
        _toBeMocked.DoThing(null);
    }
}

Because the Callback is async but the signature requires an Action, meaning the awaiter is never awaited, and the test ends before the exception is thrown.因为回调是异步的,但签名需要一个动作,这意味着永远不会等待等待者,并且测试在抛出异常之前结束。

Is there any functionality in Moq to await an asynchronous callback? Moq 中是否有等待异步回调的功能?

Edit编辑

There seems to be some confusion.似乎有些混乱。 I hope this clarifies the problem.我希望这可以澄清问题。

I'm doing TDD.我正在做 TDD。 I've implemented the simplest shell of the code to make the test compile.我已经实现了最简单的代码外壳来进行测试编译。 Then I have written the test to ensure "ABC" is the result of the Task and I've set the test running.然后我编写了测试以确保“ABC”是Task的结果,并且我已经设置了测试运行。 It's passing.它正在过去。 This is the issue .这就是问题所在 I want the test to fail, so I can implement the 'real' implementation.我希望测试失败,这样我就可以实现“真正的”实现。

Edit 2编辑 2

The more I think about this the more I think this is probably impossible.我越想越觉得这可能是不可能的。 I've opened an issue for the repo: https://github.com/moq/moq4/issues/737 but I was thinking about the implementation of such a feature as I was writing the request in anticipation of submitting a PR, and it seems impossible.我已经为 repo 打开了一个问题: https ://github.com/moq/moq4/issues/737 但我正在考虑实现这样一个功能,因为我正在编写请求以预期提交 PR,并且这似乎是不可能的。 Still if anyone has any ideas I'd love to hear them either here or in the GitHub issue and I'll keep both places up to date.不过,如果有人有任何想法,我很乐意在这里或在 GitHub 问题中听到他们的意见,我会及时更新这两个地方。 For now, I suppose I will have to use a stub.现在,我想我将不得不使用存根。

The call back expects an Action, you attempt to perform an async operation in said callback which boils down to async void call.回调需要一个 Action,您尝试在所述回调中执行异步操作,归结为async void调用。 Exceptions cannot be caught in such situations as they are fire and forget.在这种情况下不能捕获异常,因为它们是一劳永逸的。

Reference Async/Await - Best Practices in Asynchronous Programming参考Async/Await - 异步编程的最佳实践

So the problem is not with the Moq framework but rather the approach taken.所以问题不在于 Moq 框架,而在于所采用的方法。

Use the call back to get the desired parameter and work from there.使用回调获取所需的参数并从那里开始工作。

Review the progression of the following tests to see how the TDD approach evolves with each test.查看以下测试的进度,了解 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);
    }
}

It MAY or MAY NOT actually work if you use an async method signature on the callback.如果您在回调上使用异步方法签名,它可能会或可能不会实际工作。 Depends if your runtime decides to continue with the same thread or spin up a new one.取决于您的运行时是决定继续使用同一个线程还是启动一个新线程。 If it makes sense, and it often does, to do some Asserts or grab some parameters for later validation in the Callback, you should remove the async stuff all together and force it to run via如果在回调中做一些断言或获取一些参数以供以后验证是有意义的,而且它经常这样做,你应该一起删除异步的东西并强制它通过运行

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM