0

I am trying to unit test an async method below using VSTest. However, the test passes for AsyncMathsStatic.Divide(4, 0) as well as AsyncMathsStatic.Divide(4, 1)) even though an exception is thrown in the first case only.

[TestClass]
public class UnitTest1
{
    [TestMethod]

    public void DivideTest1()
    {
        // Func<Task> action = async()=> {  AsyncMathsStatic.Divide(4, 0); };
        //action.Should().Throw<DivideByZeroException>();

        Assert.ThrowsExceptionAsync<DivideByZeroException>(async () => 
        AsyncMathsStatic.Divide(4, 0));
    }
}

public class AsyncMathsStatic
{
    public static async void Divide(int v1, int v2)
    {
        try
        {
            if (v1/v2 > 1)
            {
                // do something time consuming
            }   
        }
        catch (DivideByZeroException ex)
        {
            throw;
        }
    }
}
3
  • The code, as shown, should pass the test. What are you not showing? Ignoring compiler errors and using async void can get you into a lot of trouble. Commented Dec 17, 2019 at 8:42
  • It passes the test for both cases when there is exception or not. That’s all the code there is. Commented Dec 17, 2019 at 10:17
  • Your comment about async void helped me to change it to async Task. Commented Dec 17, 2019 at 14:21

3 Answers 3

2

It's important to understand how asynchronous methods work to see what's going on here. All asynchronous methods start running synchronously, just like any other method. But at the first await that acts on an incomplete Task, the method returns. Usually it would return its own Task that the caller can then await.

But if the method signature is async void, then it returns nothing. At that point, the method has not completed running, but the calling method will never know when or if it finished.

That's likely what's happening here. The method is returning when it hits the first await and it thinks the test completed successfully. It will never see the exception that gets thrown later.

The fix is to return a Task, so the method can be awaited:

public static async Task Divide(int v1, int v2)

The only legitimate use for async void is for event handlers, because you have no choice but to make them void. But also it's usually ok there, since events are supposed to be a "oh by the way this happened" and successful completion of the event handler doesn't usually affect the further operation of whatever called it (but there are some exceptions to that).

Sign up to request clarification or add additional context in comments.

4 Comments

That I would expect to cause the reported. But there's nothing async and awaitable in that code.
@PauloMorgado I'm assuming there's an await somewhere in the // do something time consuming.
I asked, and the answer was "That’s all the code there is.".
@PauloMorgado I know, I saw :) I don't believe it.
0

If your testing environment doesn't allow the use of asynchronous Test Methods use something like the following:

       [TestMethod]
    public void TestMethod1()
    {
        Task.Run(async () =>
        {
           // test setup

            var result = await SomeAsyncTask();
            Assert.IsTrue(result.Id != 0, "Id not changed");

        }).GetAwaiter().GetResult();
    }

As pointed out by @Paulo if your testing environment does allow for asynchronous Test Methods then you can use:

      [TestMethod]
    public async Task TestMethod1()
    {
       // test setup

        var result = await SomeAsyncMethod();
        Assert.IsTrue(result.Id != 0, "Id not changed");
    }

2 Comments

Why the use of Task.Run. And you can use async Task instead of void methods as test methods.
Old habits. I've posted some clarification.
0

Even if the method is synchronous, the execution will always be synchronous up until the first await on a non-completed task. Your code doesn't even have awaits.

async void methods are unawaitable.

An unawaited task is returned by a method as if the method was an async void.

The following code shows all this situations:

static async Task Main()
{
    try
    {
        Console.WriteLine("Before calling "+ nameof(AsyncMathsStatic.Divide1));
        var t = AsyncMathsStatic.Divide1(4, 0);
        Console.WriteLine("After calling "+ nameof(AsyncMathsStatic.Divide1));
        await t;
    }
    catch (DivideByZeroException ex)
    {
        Console.WriteLine("Exception thrown in " + nameof(Main));
    }

    try
    {
        Console.WriteLine("Before calling "+ nameof(AsyncMathsStatic.Divide2));
        var t = AsyncMathsStatic.Divide2(4, 0);
        Console.WriteLine("After calling "+ nameof(AsyncMathsStatic.Divide2));
        await t;
    }
    catch (DivideByZeroException ex)
    {
        Console.WriteLine("Exception thrown in " + nameof(Main));
    }
}

public class AsyncMathsStatic
{
    public static async Task Divide1(int v1, int v2)
    {
        try
        {
            Console.WriteLine(nameof(Divide1) + ".1");
            await Task.Yield();
            Console.WriteLine(nameof(Divide1) + ".2");
            if (v1 / v2 > 1)
            {
                await Task.Yield();
            }
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Exception thrown in " + nameof(Divide1));
            throw;
        }
    }
    public static async Task Divide2(int v1, int v2)
    {
        try
        {
            Console.WriteLine(nameof(Divide2) + ".1");
            await Task.CompletedTask;
            Console.WriteLine(nameof(Divide2) + ".2");
            if (v1 / v2 > 1)
            {
                await Task.Yield();
            }
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Exception thrown in " + nameof(Divide2));
            throw;
        }
    }
}

/* Output
Before calling Divide1
Divide1.1
After calling Divide1
Divide1.2
Exception thrown in Divide1
Exception thrown in Main
Before calling Divide2
Divide2.1
Divide2.2
Exception thrown in Divide2
After calling Divide2
Exception thrown in Main
*/

This proves that your could should have thrown if it's exactly as you posted.

Am I missing something here?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.