Nunit异步测试异常断言

Sri*_*lan 37 c# nunit async-await

我有一个控制器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)));
                });
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试为我抛出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>());
}
Run Code Online (Sandbox Code Playgroud)

结果 测试失败

  Expected: instance of <System.Web.Http.HttpResponseException>
  But was:  no exception thrown
Run Code Online (Sandbox Code Playgroud)

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));
}
Run Code Online (Sandbox Code Playgroud)

结果 测试失败

  Expected: <System.Web.Http.HttpResponseException>
  But was:  <System.AggregateException> (One or more errors occurred.)
Run Code Online (Sandbox Code Playgroud)

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));
}
Run Code Online (Sandbox Code Playgroud)

结果 测试失败

  Expected: <System.Web.Http.HttpResponseException>
  But was:  null
Run Code Online (Sandbox Code Playgroud)

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");
}
Run Code Online (Sandbox Code Playgroud)

结果 测试通过

问题 -

  1. 当ExpectedException出现时,Assert.Throws为什么不处理HttpResponseException?
  2. 我不想只测试抛出异常.我想在响应的状态代码上断言.这样做的方法是什么?

对这些行为及其原因的任何比较都会很棒!

Ste*_*ary 48

你看到的问题是由于async void.

特别是:

1)async () => await userController.Get("foo")被转换为TestDelegate,返回void,所以你的lambda表达式被视为async void.因此测试运行器将开始执行lambda但不等待它完成.lambda在Get完成之前返回(因为它是async),并且测试运行器看到它返回时没有异常.

2)Wait包装任何例外AggregateException.

3)同样,asynclambda被视为async void,因此测试运行器不等待其完成.

4)我建议你做这个,async Task而不是async void,但在这种情况下,测试运行器确实等待完成,从而看到异常.

根据这个错误报告,在下一个NUnit版本中有一个修复.在此期间,您可以构建自己的ThrowsAsync方法; xUnit的一个例子就在这里.

  • 自2.6.3以来,Bug已得到修复 (2认同)

Jam*_*oss 31

我不确定它何时被添加,但当前版本的Nunit(编写时为3.4.1)包含ThrowsAsync方法

请参阅https://github.com/nunit/docs/wiki/Assert.ThrowsAsync

例:

[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));
}
Run Code Online (Sandbox Code Playgroud)

  • 这个答案应该在顶部,这样人们就不会浪费时间尝试所有的定制解决方案.答案已经立即内置到NUnit中 (8认同)
  • 唷,我很高兴我没有停止向下滚动:) (2认同)

Sri*_*lan 11

这篇博客讨论了与我类似的问题.

我按照那里提出的建议,进行了这样的测试 -

    [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));
    }
Run Code Online (Sandbox Code Playgroud)

我对它不太满意,但这很有效.

编辑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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我现在可以进行类似的测试 -

    [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);
    }
Run Code Online (Sandbox Code Playgroud)


Jai*_*der 5

这是文档中的示例:

var ex = Assert.ThrowsAsync<ArgumentException>(async () => await MethodThatThrows());
Run Code Online (Sandbox Code Playgroud)
  1. 使用ThrowsAsync
  2. 使用async/await

https://docs.nunit.org/articles/nunit/writing-tests/assertions/classic-assertions/Assert.ThrowsAsync.html

  • 使用 NUnit 3.12.0 似乎不需要步骤 2,因此以下内容仍然按预期工作: `var ex = Assert.ThrowsAsync&lt;ArgumentException&gt;(() =&gt; MethodThatThrows());` 我正在使用这种方法我的测试。这也正如@james-ross 在他的回答中所建议的那样:/sf/answers/2802169191/(见上文)。 (2认同)