Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a breakpoint after await in unit test?

I am surprised my breakpoint after awaiting an async method that is on a line that references the awaited Task<T> is never hit:

    [Test]
    public async void GetSomethingTest()
    {
        var service = SimpleIoc.Default.GetInstance<IService>();
        var result = await service.TryGetSomethingAsync(20);
        Assert.IsTrue(result.IsSuccess);
        Assert.IsNotNull(result.ReturnValue);
    }

Putting a breakpoint on the first Assert line is never hit but the test passes.

How to I break when await returns?

UPDATE: I guess it is because the test framework is not awaiting the invocation of the test method, I am using NUnit 2.6.3 and that claims async support, however whether that entails breaking after an await like I am trying to do, I am not sure...

like image 595
markmnl Avatar asked Jul 18 '14 04:07

markmnl


1 Answers

The problem is that your method is async void. That has fire-and-forget semantics.

Conceptually what your method is doing with the async-await usage looks like this:

[Test]
public void GetSomethingTest()
{
    var service = SimpleIoc.Default.GetInstance<IService>();
    service.TryGetSomethingAsync(20).ContinueWith(t =>
    {
        var result = t.Result;
        Assert.IsTrue(result.IsSuccess);
        Assert.IsNotNull(result.ReturnValue);
    });
}

Now it should be clear what the problem is. Your test method immediately returns as soon as TryGetSomethingAsync returns its Task. So the test immediately finishes. Since no exceptions were thrown, it is a success.

If your test framework supports Task-returning tests, you can fix your test to do what you want by simply changing its return type to Task instead of void.

[Test]
public async Task GetSomethingTest()
{
    var service = SimpleIoc.Default.GetInstance<IService>();
    var result = await service.TryGetSomethingAsync(20);
    Assert.IsTrue(result.IsSuccess);
    Assert.IsNotNull(result.ReturnValue);
}

This will conceptually translate to the following.

[Test]
public Task GetSomethingTest()
{
    var service = SimpleIoc.Default.GetInstance<IService>();
    return service.TryGetSomethingAsync(20).ContinueWith(t =>
    {
        var result = t.Result;
        Assert.IsTrue(result.IsSuccess);
        Assert.IsNotNull(result.ReturnValue);
    });
}

Notice how the Task continuation is returned, so that the test framework can now wait on it, ensuring that all the test's code has time to run before the test is considered finished.

(Technically a framework could be made to work in the async void case as well, but I don't know of any reason why that would be a good feature, so I expect most don't handle it.)

If your test framework does not support Task-returning tests, you can fix your test by using .Result instead of await.

[Test]
public void GetSomethingTest()
{
    var service = SimpleIoc.Default.GetInstance<IService>();
    var result = service.TryGetSomethingAsync(20).Result;
    Assert.IsTrue(result.IsSuccess);
    Assert.IsNotNull(result.ReturnValue);
}

This will simply block the current thread until the Task returned by TryGetSomethingAsync is completed.

like image 114
Timothy Shields Avatar answered Oct 19 '22 06:10

Timothy Shields