[英]Nunit async test exception assertion
[编辑(2020 年 5 月)] - 据报道,此问题已在 NUnit 的较新版本中得到解决。 请参阅Nunit.ThrowsAsync 。 (参考这个答案,谢谢@James-Ross)
我有一个带有此操作的控制器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)));
});
}
我正在尝试为抛出 404 异常的情况编写测试。
这是我尝试过的,输出 -
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>());
}
结果测试失败
Expected: instance of <System.Web.Http.HttpResponseException>
But was: no exception thrown
[测试] public void someTest() { var mockUserRepository = new Mock(); mockUserRepository.Setup(x => x.GetByUserName(It.IsAny())).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));
}
结果测试失败
Expected: <System.Web.Http.HttpResponseException>
But was: <System.AggregateException> (One or more errors occurred.)
[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));
}
结果测试失败
Expected: <System.Web.Http.HttpResponseException>
But was: null
[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");
}
结果测试通过
问题 -
对这些行为及其原因的任何比较都会很棒!
由于async void
您看到了问题。
特别是:
async () => await userController.Get("foo")
被转换为TestDelegate
,它返回void
,因此您的 lambda 表达式被视为async void
。 因此,测试运行器将开始执行 lambda,但不会等待它完成。 lambda 在Get
完成之前返回(因为它是async
),并且测试运行程序看到它无异常地返回。
Wait
将所有异常包装在AggregateException
。
同样, async
lambda 被视为async void
,因此测试运行程序不会等待其完成。
我建议您创建此async Task
而不是async void
,但在这种情况下,测试运行程序确实等待完成,因此会看到异常。
根据这个错误报告,在 NUnit 的下一个版本中会有一个修复程序。 同时,您可以构建自己的ThrowsAsync
方法; xUnit 的一个例子是here 。
我不确定它是何时添加的,但当前版本的 Nunit(撰写本文时为 3.4.1)包含一个 ThrowsAsync 方法
见https://github.com/nunit/docs/wiki/Assert.ThrowAsync
例子:
[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));
}
这个博客讨论了与我类似的问题。
我遵循了那里提出的建议,并进行了这样的测试-
[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));
}
我对它不太满意,但这有效。
编辑 1
受@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);
}
}
我现在可以进行类似的测试 -
[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);
}
如果您等待任务,则抛出的异常将聚合到 AggregateException 中。 您可以检查 AggregateException 的内部异常。 这可能是您的案例 2 不起作用的原因。
由在任务内运行的用户代码引发的未处理异常将传播回加入线程,但在本主题后面描述的某些情况下除外。 当您使用静态或实例 Task.Wait 或 Task.Wait 方法之一并通过将调用包含在 try-catch 语句中来处理它们时,会传播异常。 如果任务是附加子任务的父任务,或者您正在等待多个任务,则可能会引发多个异常。 为了将所有异常传播回调用线程,Task 基础结构将它们包装在一个 AggregateException 实例中。 AggregateException 有一个 InnerExceptions 属性,可以枚举它来检查所有抛出的原始异常,并单独处理(或不处理)每个异常。 即使只抛出一个异常,它仍然包含在一个 AggregateException 中。
我有一个类似的问题,你在场景 3 测试用例由于以下结果失败
Expected: <UserDefineException>
But was: null
通过使用 Assert.ThrowAsync<> 问题得到解决
我的 Web API 操作方法和单元测试用例方法如下
public async Task<IHttpActionResult> ActionMethod(RequestModel requestModel)
{
throw UserDefineException();
}
[Test]
public void Test_Contrller_Method()
{
Assert.ThrowsAsync<UserDefineException>(() => _controller.ActionMethod(new RequestModel()));
}
这是文档中的一个示例:
var ex = Assert.ThrowsAsync<ArgumentException>(async () => await MethodThatThrows());
ThrowsAsync
async
/ await
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.