使用 Moq 模拟单元测试的异步方法
我正在测试用于进行 Web API
调用的服务的方法.如果我还在本地运行 Web 服务(位于解决方案中的另一个项目中),则使用普通的 HttpClient
可以很好地进行单元测试.
I am testing a method for a service that makes a Web API
call. Using a normal HttpClient
works fine for unit tests if I also run the web service (located in another project in the solution) locally.
但是,当我签入更改时,构建服务器将无法访问 Web 服务,因此测试将失败.
However when I check in my changes the build server won't have access to the web service so the tests will fail.
我通过创建一个 IHttpClient
接口并实现我在应用程序中使用的版本,为我的单元测试设计了一种方法.对于单元测试,我使用模拟的异步 post 方法制作了一个完整的模拟版本.这是我遇到问题的地方.我想为这个特定的测试返回一个 OK HttpStatusResult
.对于另一个类似的测试,我将返回一个糟糕的结果.
I've devised a way around this for my unit tests by creating an IHttpClient
interface and implementing a version that I use in my application. For unit tests, I make a mocked version complete with a mocked asynchronous post method. Here's where I have run into problems. I want to return an OK HttpStatusResult
for this particular test. For another similar test I will be returning a bad result.
测试将运行但永远不会完成.它挂在等待.我是异步编程、委托和 Moq 本身的新手,我一直在搜索 SO 和 google 一段时间以学习新事物,但我似乎仍然无法解决这个问题.
The test will run but will never complete. It hangs at the await. I am new to asynchronous programming, delegates, and Moq itself and I've been searching SO and google for a while learning new things but I still can't seem to get past this problem.
这是我要测试的方法:
public async Task<bool> QueueNotificationAsync(IHttpClient client, Email email)
{
// do stuff
try
{
// The test hangs here, never returning
HttpResponseMessage response = await client.PostAsync(uri, content);
// more logic here
}
// more stuff
}
这是我的单元测试方法:
Here's my unit test method:
[TestMethod]
public async Task QueueNotificationAsync_Completes_With_ValidEmail()
{
Email email = new Email()
{
FromAddress = "bob@example.com",
ToAddress = "bill@example.com",
CCAddress = "brian@example.com",
BCCAddress = "ben@example.com",
Subject = "Hello",
Body = "Hello World."
};
var mockClient = new Mock<IHttpClient>();
mockClient.Setup(c => c.PostAsync(
It.IsAny<Uri>(),
It.IsAny<HttpContent>()
)).Returns(() => new Task<HttpResponseMessage>(() => new HttpResponseMessage(System.Net.HttpStatusCode.OK)));
bool result = await _notificationRequestService.QueueNotificationAsync(mockClient.Object, email);
Assert.IsTrue(result, "Queue failed.");
}
我做错了什么?
感谢您的帮助.
您正在创建一项任务,但从未启动它,因此它永远不会完成.但是,不要只是开始任务 - 而是改为使用 Task.FromResult
这会给你一个已经完成的任务:
You're creating a task but never starting it, so it's never completing. However, don't just start the task - instead, change to using Task.FromResult<TResult>
which will give you a task which has already completed:
...
.Returns(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)));
请注意,您不会以这种方式测试实际的异步 - 如果您想这样做,您需要做更多的工作来创建一个您可以控制的 Task
以更细粒度的方式......但那是另一天的事情.
Note that you won't be testing the actual asynchrony this way - if you want to do that, you need to do a bit more work to create a Task<T>
that you can control in a more fine-grained manner... but that's something for another day.
您可能还想考虑为 IHttpClient
使用一个假的,而不是模拟一切——这实际上取决于您需要它的频率.
You might also want to consider using a fake for IHttpClient
rather than mocking everything - it really depends on how often you need it.