如何告诉Moq返回一个Task?

447

我有一个声明接口的界面

Task DoSomethingAsync();

我正在使用MoqFramework进行我的测试:

[TestMethod()]
public async Task MyAsyncTest()
{
   Mock<ISomeInterface> mock = new Mock<ISomeInterface>();
   mock.Setup(arg => arg.DoSomethingAsync()).Callback(() => { <my code here> });
   ...
}

然后在我的测试中,我执行了调用await DoSomethingAsync()的代码。但是测试就在那一行失败了。我做错了什么?


5
当您说测试在那一行出错时,它会产生什么错误? - AlSki
@AlSki 可能是一个 NullReferenceException 异常。你可以在这里看到。 - LuckyLikey
5个回答

923

你的方法没有任何回调函数,因此没有理由使用 .CallBack()。 你可以使用 .Returns()Task.FromResult 返回一个带有所需值的任务,例如:

MyType someValue=...;
mock.Setup(arg=>arg.DoSomethingAsync())        
    .Returns(Task.FromResult(someValue));

更新于2014-06-22

Moq 4.2有两个新的扩展方法来帮助处理此问题。

mock.Setup(arg=>arg.DoSomethingAsync())
    .ReturnsAsync(someValue);

mock.Setup(arg=>arg.DoSomethingAsync())        
    .ThrowsAsync(new InvalidOperationException());

更新2016-05-05

正如Seth Flowers在其他回答中提到的那样,ReturnsAsync仅适用于返回Task<T>的方法。对于仅返回Task的方法,

.Returns(Task.FromResult(default(object)))

可以使用。

此答案所示,在.NET 4.6中,这被简化为.Returns(Task.CompletedTask);,例如:

mock.Setup(arg=>arg.DoSomethingAsync())        
    .Returns(Task.CompletedTask);

29
回归(Task.CompletedTask); 这是我的答案。 - Todd Vance
12
感谢您将此答案保持最新状态,因为 Moq 框架已经更新! - Jacob Stamm
当返回类型为void时,.Returns(Task.FromResult(default(object)))运行良好。当期望的返回类型为null时,.Returns(Task.FromResult(null as MyType))运行良好。 - Jeremy Ray Brown
1
@JeremyRayBrown,我解释一下,在.NET 4.6中,default(object)不再需要。 只要MyType是引用类型,null as MyTypedefault(MyType)相同。 - Panagiotis Kanavos
现在在 .NET 中,Mock 支持 ReturnAsync 并直接返回您的对象,无需使用 Task.CompletedTask 或 FromResult。 - Ahmed Magdy

44

类似问题

我有一个界面,大致上看起来像这样:

Task DoSomething(int arg);

症状

我的单元测试在被测试的服务awaited调用DoSomething时失败了。

解决方法

在这种情况下,与接受的答案不同,您无法在此方法的Setup()上调用.ReturnsAsync(),因为该方法返回非泛型Task而不是Task<T>

然而,您仍然可以在设置中使用.Returns(Task.FromResult(default(object)))来使测试通过。


1
关于这个问题,我有一个想法。如果您需要返回一个非泛型任务(非 .net 4.6),我建议您考虑返回 Task.Delay(1) 作为返回任务的简单方式。您还可以通过增加时间参数来模拟工作情况。 - stevethethread

30

只需要在回调函数后添加.Returns(Task.FromResult(0));即可。

示例:

mock.Setup(arg => arg.DoSomethingAsync())
    .Callback(() => { <my code here> })
    .Returns(Task.FromResult(0));

4
现在,您还可以使用Talentsoft.Moq.SetupAsync软件包https://github.com/TalentSoft/Moq.SetupAsync。该软件包基于此处找到的答案和提出的想法,但尚未在此处实现:https://github.com/moq/moq4/issues/384,它大大简化了异步方法的设置。
以前的回复中发现了一些使用SetupAsync扩展的示例:
mock.SetupAsync(arg=>arg.DoSomethingAsync());
mock.SetupAsync(arg=>arg.DoSomethingAsync()).Callback(() => { <my code here> });
mock.SetupAsync(arg=>arg.DoSomethingAsync()).Throws(new InvalidOperationException());

0

感谢被接受的答案提供了非常有帮助的解决方案。

我已经在我们的项目中添加了两个扩展方法(针对TaskValueTask),以使习惯于在所有异步设置上调用.ReturnsAsync的人更容易发现它们。

public static class MoqExtensions
{
    public static IReturnsResult<TMock> ReturnsAsync<TMock>(this IReturns<TMock, Task> mock)
        where TMock : class
    {
        return mock.Returns(Task.CompletedTask);
    }

    public static IReturnsResult<TMock> ReturnsAsync<TMock>(this IReturns<TMock, ValueTask> mock) 
        where TMock : class
    {
        return mock.Returns(ValueTask.CompletedTask);
    }
}

可以按如下方式调用:

mock.Setup(x => x.DoSomethingAsync())        
    .ReturnsAsync();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接