13

I have the following method in an interface..

Task<SearchResult<T>> SearchAsync(TU searchOptions);

works great.

Now i'm trying to make a unit test to test when something goes wrong - and the code throws an exception.

In this case, I've setup my method to throw an HttpRequestException. My unit test fails to say that I threw that exception ..

var result = Should.Throw<HttpRequestException>
    (async () => await service.SearchAsync(searchOptions));

the error message from the unit test is

Shouldly.ChuckedAWobbly
var result = Should
throw
System.Net.Http.HttpRequestException
but does not

So the assertion framework is saying: You've expected an exception, but none was thrown.

When I step -through- the code, the exception is 100% thrown.

Can anyone see what i've done wrong with my unit test code, please?

Pure.Krome
  • 84,693
  • 113
  • 396
  • 647
  • Is the exception by any change caught by something in between? – elnigno Mar 14 '14 at 10:49
  • Does this answer your question? [Unit testing async method for specific exception](https://stackoverflow.com/questions/12837128/unit-testing-async-method-for-specific-exception) – Liam Aug 21 '20 at 09:59

6 Answers6

14

The problem is that your assertion framework does not understand asynchronous methods. I recommend you raise an issue with them.

In the meantime, you can use the source for Should.Throw to write your own MyShould.ThrowAsync:

public static async Task<TException> ThrowAsync<TException>(Func<Task> actual)
    where TException : Exception
{
  try
  {
    await actual();
  }
  catch (TException e)
  {
    return e;
  }
  catch (Exception e)
  {
    throw new ChuckedAWobbly(new ShouldlyMessage(typeof(TException), e.GetType()).ToString());
  }

  throw new ChuckedAWobbly(new ShouldlyMessage(typeof(TException)).ToString());
}

And use it as such:

var result = await MyShould.ThrowAsync<HttpRequestException>
    (async () => await service.SearchAsync(searchOptions));

or the slightly simpler and equivalent:

var result = await MyShould.ThrowAsync<HttpRequestException>
    (() => service.SearchAsync(searchOptions));
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 2
    FYI the task overloads are in another file since v2 (Feb 2014). https://github.com/shouldly/shouldly/blob/master/src/Shouldly/ShouldThrowTaskExtensions.cs#L10 Shouldly does block though so the test doesn't need to await the Should.Throw method. I think we will add Should.ThrowAsync which is asynchronous though – Jake Ginnivan Jan 21 '15 at 14:17
4

Test it like this:

var result = Should.Throw<HttpRequestException>
    (() => service.SearchAsync(searchOptions).Result);

Or:

var result = Should.Throw<HttpRequestException>
    (() => service.SearchAsync(searchOptions).Wait());

Otherwise, your Should.Throw returns before the async lambda has completed.

avo
  • 10,101
  • 13
  • 53
  • 81
  • First line doesn't compile. – Pure.Krome Mar 14 '14 at 11:34
  • @Pure.Krome, it means `Should.Throw` expects a `void` lambda, as far as can tell. The second one does the same, without peeking into the result. – avo Mar 14 '14 at 12:20
  • 4
    `Result` and `Wait` both wrap exceptions into an `AggregateException`, so this answer won't work as-is. – Stephen Cleary Mar 14 '14 at 13:49
  • 1
    This can also cause a deadlock if there is a synchronization context set here, as you're synchronously blocking on an asynchronous method. – Servy Mar 14 '14 at 13:51
3

Unit testing async code/functionality is pretty hard. I myself am getting into unit testing async and running into the same problems as you do.

I found the following two resources very helpful:

sTodorov
  • 5,435
  • 5
  • 35
  • 55
3

The problem is that the passed lambda returns a Task. The thrown exception can only be observed by Should.Throw if it waits for this task to complete, which apparently it does not. As a work-around, you can .Wait on the task returned by SearchAsync yourself.

mstest (the built-in visual studio testing framework) has support for async tests since Visual Studio 2012. You can basically just change the test method declaration by replacing "void" by "async Task".

[TestMethod]
[ExpectedException(typeof(System.Net.Http.HttpRequestException))]
public async Task SomeTest()
{
   await service.SearchAsync(searchOptions);
}

You are probably using a different unit testing framework, but it's not clear which one. Consult its documentation to see if it supports async tests.

NUnit 2.6.3 also seems to support async tests.

edit: so you are using xUnit. This particular issue was fixed for xUnit 2.0. It's currently still alpha though.

Wim Coenen
  • 66,094
  • 13
  • 157
  • 251
  • 1
    The passed lambda actually returns `void`, and it's very hard to catch exceptions from `async void` methods. `Wait` wraps its exceptions in `AggregateException`, so it doesn't work. – Stephen Cleary Mar 14 '14 at 13:50
2

The exception is thrown on a different thread to that on which your unit test runs. The unit test framework can only anticipate exceptions on its own thread.

I would suggest you test the exception on a synchronous version of the service.

Chris Ballard
  • 3,771
  • 4
  • 28
  • 40
1

Today Shouldly contains Should.ThrowAsync<T>.

You can use it like this:

await Should.ThrowAsync<HttpRequestException>(() => service.SearchAsync(searchOptions));
Piotr Perak
  • 10,718
  • 9
  • 49
  • 86