Srikanth Venugopalan Srikanth Venugopalan - 1 month ago 28
C# Question

Nunit async test exception assertion

I have a controller

UserController
with this action

// 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.

Here is what I have tried, with the output -

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>());
}


Result
Test Failed

Expected: instance of <System.Web.Http.HttpResponseException>
But was: no exception thrown


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));
}


Result
Test failed

Expected: <System.Web.Http.HttpResponseException>
But was: <System.AggregateException> (One or more errors occurred.)


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));
}


Result
Test Failed

Expected: <System.Web.Http.HttpResponseException>
But was: null


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");
}


Result
Test passes

Questions -


  1. Why would Assert.Throws not handle HttpResponseException, when ExpectedException does?

  2. 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?



Any comparision on these behaviour and its cause(s) would be great!

Answer

You're seeing problems due to async void.

In particular:

1) async () => await userController.Get("foo") is converted into TestDelegate, which returns void, so your lambda expression is treated as async void. So the test runner will begin executing the lambda but not wait for it to complete. The lambda returns before Get completes (because it's async), and the test runner sees that it returned without an exception.

2) Wait wraps any exceptions in an AggregateException.

3) Again, the async lambda is being treated as async void, so the test runner is not waiting for its completion.

4) I recommend you make this async Task rather than async void, but in this case the test runner does wait for completion, and thus sees the exception.

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.