C# Nunit 异步测试异常断言
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15634542/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Nunit async test exception assertion
提问by Srikanth Venugopalan
I have a controller UserController
with this action
我有一个UserController
执行此操作的控制器
// GET /blah
public Task<User> Get(string domainUserName)
{
if (string.IsNullOrEmpty(domainUserName))
{
throw new ArgumentException("No username specified.");
}
return Task.Factory.StartNew(
() =>
{
var user = userRepository.GetByUserName(domainUserName);
if (user != null)
{
return user;
}
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, string.Format("{0} - username does not exist", domainUserName)));
});
}
I am trying to write a test for the case where I throw a 404 exception.
我正在尝试为抛出 404 异常的情况编写测试。
Here is what I have tried, with the output -
这是我尝试过的,输出 -
1)
1)
[Test]
public void someTest()
{
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
Assert.That(async () => await userController.Get("foo"), Throws.InstanceOf<HttpResponseException>());
}
ResultTest Failed
结果测试失败
Expected: instance of <System.Web.Http.HttpResponseException>
But was: no exception thrown
2)
2)
[Test]
public void someTest()
{
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
var httpResponseException = Assert.Throws<HttpResponseException>(() => userController.Get("foo").Wait());
Assert.That(httpResponseException.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
ResultTest failed
结果测试失败
Expected: <System.Web.Http.HttpResponseException>
But was: <System.AggregateException> (One or more errors occurred.)
3)
3)
[Test]
public void someTest()
{
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
var httpResponseException = Assert.Throws<HttpResponseException>(async () => await userController.Get("foo"));
Assert.That(httpResponseException.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
ResultTest Failed
结果测试失败
Expected: <System.Web.Http.HttpResponseException>
But was: null
4)
4)
[Test]
[ExpectedException(typeof(HttpResponseException))]
public async void ShouldThrow404WhenNotFound()
{ var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
var task = await userController.Get("foo");
}
ResultTest passes
结果测试通过
Questions -
问题 -
- Why would Assert.Throws not handle HttpResponseException, when ExpectedException does?
- I don't want to just test that exception is thrown. I want to assert on the Status Code of the response. What's the way to do this?
- 为什么 Assert.Throws 不处理 HttpResponseException,而 ExpectedException 呢?
- 我不想只是测试抛出异常。我想对响应的状态代码进行断言。有什么方法可以做到这一点?
Any comparision on these behaviour and its cause(s) would be great!
对这些行为及其原因的任何比较都会很棒!
采纳答案by Stephen Cleary
You're seeing problems due to async void
.
由于async void
.
In particular:
特别是:
async () => await userController.Get("foo")
is converted intoTestDelegate
, which returnsvoid
, so your lambda expression is treated asasync void
. So the test runner will begin executing the lambda but not wait for it to complete. The lambda returns beforeGet
completes (because it'sasync
), and the test runner sees that it returned without an exception.Wait
wraps any exceptions in anAggregateException
.Again, the
async
lambda is being treated asasync void
, so the test runner is not waiting for its completion.I recommend you make this
async Task
rather thanasync void
, but in this case the test runner does wait for completion, and thus sees the exception.
async () => await userController.Get("foo")
转换为TestDelegate
,返回void
,因此您的 lambda 表达式被视为async void
。因此,测试运行器将开始执行 lambda,但不会等待它完成。lambda 在Get
完成之前返回(因为它是async
),并且测试运行程序看到它没有异常地返回。Wait
将任何异常包装在AggregateException
.同样,
async
lambda 被视为async void
,因此测试运行程序不会等待其完成。我建议您使用 this
async Task
而不是async void
,但在这种情况下,测试运行程序确实等待完成,因此会看到异常。
According to this bug report, there is a fix for this coming in the next build of NUnit. In the meantime, you can build your own ThrowsAsync
method; an example for xUnit is here.
根据这个错误报告,在 NUnit 的下一个版本中会有一个修复程序。同时,您可以构建自己的ThrowsAsync
方法;xUnit 的一个例子是here。
回答by roqz
If you await a Task then Exceptions that are thrown are aggregated into AggregateException. You can inspect the inner exceptions of AggregateException. This could be the reason why you case 2 does not work.
如果您等待任务,则抛出的异常将聚合到 AggregateException 中。您可以检查 AggregateException 的内部异常。这可能是您的案例 2 不起作用的原因。
Unhandled exceptions that are thrown by user code that is running inside a task are propagated back to the joining thread, except in certain scenarios that are described later in this topic. Exceptions are propagated when you use one of the static or instance Task.Wait or Task.Wait methods, and you handle them by enclosing the call in a try-catch statement. If a task is the parent of attached child tasks, or if you are waiting on multiple tasks, then multiple exceptions could be thrown. To propagate all the exceptions back to the calling thread, the Task infrastructure wraps them in an AggregateException instance. The AggregateException has an InnerExceptions property that can be enumerated to examine all the original exceptions that were thrown, and handle (or not handle) each one individually. Even if only one exception is thrown, it is still wrapped in an AggregateException.
由在任务内运行的用户代码引发的未处理异常将传播回加入线程,但在本主题后面描述的某些情况下除外。当您使用静态或实例 Task.Wait 或 Task.Wait 方法之一并通过将调用包含在 try-catch 语句中来处理它们时,会传播异常。如果任务是附加子任务的父任务,或者您正在等待多个任务,则可能会引发多个异常。为了将所有异常传播回调用线程,Task 基础结构将它们包装在一个 AggregateException 实例中。AggregateException 有一个 InnerExceptions 属性,可以枚举它来检查所有抛出的原始异常,并单独处理(或不处理)每个异常。即使只抛出一个异常,
回答by Srikanth Venugopalan
This blogtalks about problems similar to mine.
这个博客讨论了与我类似的问题。
I followed the recommendation proposed there, and have a test like this -
我遵循了那里提出的建议,并进行了这样的测试-
[Test]
public void ShouldThrow404WhenNotFound()
{
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
var aggregateException = Assert.Throws<AggregateException>(() => userController.Get("foo").Wait());
var httpResponseException = aggregateException.InnerExceptions
.FirstOrDefault(x => x.GetType() == typeof(HttpResponseException)) as HttpResponseException;
Assert.That(httpResponseException, Is.Not.Null);
Assert.That(httpResponseException.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
I am not too pleased with it, but this works.
我对它不太满意,但这有效。
EDIT 1
编辑 1
Inspired by @StephenCleary, I added a static helper class that does the asserts that I am looking for. It looks like this -
受@StephenCleary 的启发,我添加了一个静态帮助器类来执行我正在寻找的断言。看起来是这样的——
public static class AssertEx
{
public static async Task ThrowsAsync<TException>(Func<Task> func) where TException : class
{
await ThrowsAsync<TException>(func, exception => { });
}
public static async Task ThrowsAsync<TException>(Func<Task> func, Action<TException> action) where TException : class
{
var exception = default(TException);
var expected = typeof(TException);
Type actual = null;
try
{
await func();
}
catch (Exception e)
{
exception = e as TException;
actual = e.GetType();
}
Assert.AreEqual(expected, actual);
action(exception);
}
}
I can now have a test like -
我现在可以进行类似的测试 -
[Test]
public async void ShouldThrow404WhenNotFound()
{
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
Action<HttpResponseException> asserts = exception => Assert.That(exception.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
await AssertEx.ThrowsAsync(() => userController.Get("foo"), asserts);
}
回答by James Ross
I'm not sure when it was added, but the current version of Nunit (3.4.1 at time of writing) includes a ThrowsAsync method
我不确定它是何时添加的,但当前版本的 Nunit(撰写本文时为 3.4.1)包含一个 ThrowsAsync 方法
see https://github.com/nunit/docs/wiki/Assert.ThrowsAsync
见https://github.com/nunit/docs/wiki/Assert.ThrowsAsync
Example:
例子:
[Test]
public void ShouldThrow404WhenNotFound()
{
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
var exception = Assert.ThrowsAsync<HttpResponseException>(() => userController.Get("foo"));
Assert.That(exception.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
回答by Niraj Trivedi
I have a similar issue that you have in scenario 3 Test case failed due to following result
我有一个类似的问题,你在场景 3 测试用例由于以下结果失败
Expected: <UserDefineException>
But was: null
by using Assert.ThrowAsync<> problem gets solved
通过使用 Assert.ThrowAsync<> 问题得到解决
My Web API action method and Unit test case method as below
我的 Web API 操作方法和单元测试用例方法如下
public async Task<IHttpActionResult> ActionMethod(RequestModel requestModel)
{
throw UserDefineException();
}
[Test]
public void Test_Contrller_Method()
{
Assert.ThrowsAsync<UserDefineException>(() => _controller.ActionMethod(new RequestModel()));
}