【问题标题】:Is it possible to call an async callback with Moq when a mocked method is called?调用模拟方法时是否可以使用 Moq 调用异步回调?
【发布时间】:2019-05-22 06:59:05
【问题描述】:

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

因为 Callback 是异步的,但签名需要一个 Action,这意味着从不等待等待者,并且测试在抛出异常之前结束。

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

编辑

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

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

编辑 2

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

【问题讨论】:

  • 在提供的示例中有些事情没有加起来。为什么要回调?你到底想测试什么? Processor 的实现看起来也不正确。
  • @Nkosi 谢谢,是的,我错过了方法调用。尝试测试以确保通过参数将“ABC”字符串传递给我的模拟,不幸的是,公开公开字符串的唯一方法是通过任务。
  • 但您将null 传递给DoThing。为什么?
  • @Nkosi 因为在 TDD 中,您会做最简单的事情来让它通过并编写测试来强制执行正确的实现。我的测试通过了,即使我的实现“明显错误”。
  • 它正在通过,因为安排的行为将按配置工作。该回调对测试没有影响,因为它在另一个线程上

标签: c# tdd moq


【解决方案1】:

回调需要一个操作,您尝试在所述回调中执行异步操作,归结为async void 调用。异常不能在这种情况下被捕获,因为它们是一劳永逸的。

参考Async/Await - Best Practices in Asynchronous Programming

所以问题不在于起订量框架,而在于所采用的方法。

使用回调获取所需的参数并从那里开始工作。

查看以下测试的进展,了解 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);
    }
}

【讨论】:

  • 当然!这是我在晚上 10 点编程时得到的。我完全忘记了我可以将参数分配给外部范围并在那里等待它。那是我看不到的解决方案!谢谢!
【解决方案2】:

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

Task.Run(() =&gt; AsyncMethodHere).Result

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-09
    • 2011-01-07
    • 1970-01-01
    • 2019-10-09
    • 1970-01-01
    • 1970-01-01
    • 2013-12-18
    相关资源
    最近更新 更多