简体   繁体   中英

Thrown Exception not caught in UnitTest try/catch

I'm designing some code right now where I'm throwing an exception if a string parameter is null or empty and the exception is thrown as it should be, but it isn't getting caught when I'm UnitTesting.

Here's the client I'm using.

public class PipeClient : IPipeClient
{
    public async void Send(string host, string pipeName, Message msg)
    {
        if (string.IsNullOrEmpty(msg.PreparedMessage))
            throw new ArgumentException("MESSAGE_NOT_FOUND");

        if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(pipeName))
            throw new ArgumentNullException();

        if (!host.TryParseHost()) 
            throw new ArgumentException("INVALID_HOST_NAME");

        using (var pipeClient = new NamedPipeClientStream(host, pipeName, PipeDirection.Out))
        {
            pipeClient.Connect(200);

            using (var writer = new StreamWriter(pipeClient))
            {
                await Task.Run(() => writer.WriteLine(msg.PreparedMessage));
                writer.Flush();
            }
        }
    }
}

And here's the UnitTest

    [TestMethod]
    public void Send_FailsOnWrongHostName()
    {
        var name = "FailWithHostname";
        var msg = new Message(MyStates.Register, "UnitTest", "Test");

        try
        {
            var client = new PipeClient();
            client.Send("lol", name, msg);
        }
        catch (Exception e)
        {
            Assert.IsTrue(e is ArgumentException);
        }
    }

So when I run that test it should as far as I know throw the exception when I call the the Send method (which is does) and then get caught in the catch clause because I'm not catching it inside the PipeClient. Yet it doesn't, it just exits with a failed test.

If you need any more information just let me know, thanks in advance.

there's a few things I want to raise in this answer. I'm not sure of your experience level so please don't think I'm being condescending at any point.

Firstly a brief note on async methods and Tasks.

  • Async void should be avoided unless in an async event handler. Async methods should return Task or Task otherwise there is nothing for the calling method to keep hold of to know when the method is done and to report back whether the method threw an exception. Async void is essentially fire and forget, there is no one left to observe the exceptions.

"In observed Tasks no one can you scream" -Me ,2018

  • Exceptions thrown in async methods are nicely unwrapped and thrown when the async method is awaited, with the call stack all preserved and reasonably sensible. If you don't await the result eventually at some point in the future you will get an UnobservedTaskException that, if you haven't configured a global handler for, will bring down your application. If you get the result of an async method synchronously using .Wait() or .Result or via .GetAwaiter().GetResult() (all 3 you should try and avoid but the 3rd option is best if you have to I have been informed), then you will get the original exception wrapped in an AggregateException.

Now if none of this is making much sense to you, I would recommend doing some reading up Tasks and async/await.

Now onto your Test.

Your method is async void so there is nothing for the calling method to have returned to it to represent the work or to let it know that the method has thrown an exception. So it carries on, the test finishes and then everything completes with no exceptions because the UnobservedTaskException can be thrown at anypoint in the future (I think it is related to when the garbage collector tidies up the faulted Task and then it throws and because the garbage collector is non-deterministic we can't say when that will happen)

So what if you made your async method return a Task??? Well that's still not quite right. You are now returning a Task that will be in a faulted state because of the exception, however because you never await it, the exception is never 'unwrapped' and actually thrown and so you're test happily continues.

What you need to do is make your Test async and return a Task and make the method you're testing async Task not async void and await that method in your test.

Like this

[TestMethod]
public async Task Send_FailsOnWrongHostName()
{
    var name = "FailWithHostname";
    var msg = new Message(MyStates.Register, "UnitTest", "Test");

    try
    {
        var client = new PipeClient();
        await client.Send("lol", name, msg);
    }
    catch (Exception e)
    {
        Assert.IsTrue(e is ArgumentException);
    }
}

public class PipeClient : IPipeClient
{
    public async Task Send(string host, string pipeName, Message msg)
    {
        if (string.IsNullOrEmpty(msg.PreparedMessage))
            throw new ArgumentException("MESSAGE_NOT_FOUND");

        if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(pipeName))
            throw new ArgumentNullException();

        if (!host.TryParseHost()) 
            throw new ArgumentException("INVALID_HOST_NAME");

        using (var pipeClient = new NamedPipeClientStream(host, pipeName, PipeDirection.Out))
        {
            pipeClient.Connect(200);

            using (var writer = new StreamWriter(pipeClient))
            {
                await Task.Run(() => writer.WriteLine(msg.PreparedMessage));
                writer.Flush();
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM