Testing for exceptions in async methods
Asked Answered
K

5

97

I'm a bit stuck with this code (this is a sample):

public async Task Fail()
{
    await Task.Run(() => { throw new Exception(); });
}

[Test]
public async Task TestFail()
{
    Action a = async () => { await Fail(); };
    a.ShouldThrow<Exception>();
}

The code doesn't catch the exception, and fails with

Expected a System.Exception to be thrown, but no exception was thrown.

I'm sure I'm missing something, but docs seem to suggest this is the way to go. Some help would be appreciated.

Killifish answered 14/3, 2017 at 15:26 Comment(6)
Yes, thank you. Missed. Doesn't fix the issue, though.Killifish
what is the issue?Concision
@stuartd Actually, no, async is req'd when testing async methods. ShouldThrow is not async. Could that be a reason?Killifish
@user227895 this works for me: Assert.CatchAsync<Exception>(async () => await Fail());Concision
Your problem is Action - this creates an async void method. The proper asynchronous delegate equivalent is Func<Task>.Accurate
There's no reason for Fail to call Task.Run, it can just throw an exception, and there's no reason for TestFail to have an anonymous method, you can just use Fail for the delegate without wrapping it in another method call that doesn't do anything.Herold
T
141

You should use Func<Task> instead of Action:

[Test]
public void TestFail()
{
    Func<Task> f = async () => { await Fail(); };
    f.ShouldThrow<Exception>();            
}

That will call the following extension which is used to verify asynchronous methods

public static ExceptionAssertions<TException> ShouldThrow<TException>(
    this Func<Task> asyncAction, string because = "", params object[] becauseArgs)
        where TException : Exception        

Internally this method will run task returned by Func and wait for it. Something like

try
{
    Task.Run(asyncAction).Wait();
}
catch (Exception exception)
{
    // get actual exception if it wrapped in AggregateException
}

Note that test itself is synchronous.

Thrave answered 14/3, 2017 at 15:37 Comment(5)
Is the async await really necessary? Seems to work fine with just Func<Task> f = () => Fail();Echovirus
It is if you are interested in waiting for the call to finish execution before the test does.Bueno
FYI - in the latest version (v5) of fluentassertions the syntax is: f.Should().Throw<Exception>Lactobacillus
If the ShouldThrow method awaits the resultant task it shouldnt matter if you await it in the func should it?Orientalize
Don't know if it's new or it was available back then already, but official website says that action can by used as well. Even though I couldn't make it work for some reason.Lentz
B
47

With Fluent Assertions v5+ the code will be like :

ISubject sut = BuildSut();
//Act and Assert
Func<Task> sutMethod = async () => { await sut.SutMethod("whatEverArgument"); };
await sutMethod.Should().ThrowAsync<Exception>();

This should work.

Baptize answered 9/1, 2019 at 8:25 Comment(5)
Is there not an await missing? await sutMethod.Should().ThrowAsync<Exception>();Downes
I think in the 3rd row, the async-await keyword-pair is unnecessary, it works for me without it as well.Unwatched
@Károly Ozsvárt : I think it works for you since the code you are testing does not have a an await that requires continuation before hitting the expected throw. This will be common for validation code in a method (since it s usual first) and might be common in a test since all dependencies are probably mocked to return completed tasks. But, it won't work if you hit a continuation.Delwyn
The synchronous version sutMethod.Should().Throw<Exception>(); worked for me as well. I think this is just blocking and I'm fine with this being a blocking call in my test.Dermatitis
How do you test message? await sutMethod.Should().ThrowAsync<Exception>().WithMessage("AlaMaKota"); ?Trichromatic
S
4

Other variation of usage ThrowAsync method:

await Should.ThrowAsync<Exception>(async () => await Fail());
Seize answered 29/9, 2021 at 12:6 Comment(0)
D
4

With Fluent Assertions v5.7 they introduced the Awaiting overload so now you can do as following:

public async void TestFail()
{
    await this.Awaiting(_ => Fail()).Should().ThrowAsync<Exception>();
}
Duplet answered 13/6, 2022 at 8:20 Comment(0)
K
0

With Fluent Assertions v6.9 you can use short form:

var act = () => Fail();
act.Should().ThrowAsync<Exception>();
Kirsch answered 9/2, 2023 at 11:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.