8

I have some integration tests using xUnit that need to tear down some resources created during the test. To do that, I have implemented IDisposable in the class containing the tests.

The problem is I need to delete resources created during the test using a client that has only an asynchronous interface. But, the Dispose method is synchronous.

I could use .Result or .Wait() to wait for the completion of the asynchronous call, but that may create deadlocks (the issue is well-documented here).

Given I cannot use .Result or .Wait(), what is the proper (and safe) way to call an asynchronous method in a Dispose method?

UPDATE: adding a (simplified) example to show the problem.

[Collection("IntegrationTests")]
public class SomeIntegrationTests : IDisposable {
    private readonly IClient _client; // SDK client for external API

    public SomeIntegrationTests() {
        // initialize client
    }

    [Fact]
    public async Task Test1() {
        await _client
            .ExecuteAsync(/* a request that creates resources */);

        // some assertions
    }

    public void Dispose() {
        _client
            .ExecuteAsync(/* a request to delete previously created resources */)
            .Wait(); // this may create a deadlock
    }
}
betabandido
  • 18,946
  • 11
  • 62
  • 76
  • Can you show me your asynchronous disposal methods? Haven't really heard of an interface that disposes itself asynchronously. Do you absolutely need to wait for the asynchronous disposal to complete before the class is disposed? Async object finalizers are not yet implemented. https://github.com/dotnet/coreclr/issues/22598 – Avin Kavish May 31 '19 at 06:56
  • @AvinKavish just added an example – betabandido May 31 '19 at 07:27
  • I'm guessing the deletion logic is unique to the test method? It should be okay to move the `teardown` logic into the test itself. The author of xUnit tells you why. https://jamesnewkirk.typepad.com/posts/2007/09/why-you-should-.html – Avin Kavish May 31 '19 at 07:35
  • 1
    Do you actually have problems using things like `.GetAwaiter().GetResult()` or are you trying to prevent issues? It's not that bad if you're not running inside a synchronisation context (UI apps..). If you don't need to "wait" for the disposal you can also trigger the async execution and not wait for it to complete - e.g. `async void` - just make sure not to cause unhandled exceptions by adding a try/catch – Martin Ullrich May 31 '19 at 07:40
  • 2
    Plus file an issue on xunit to support the upcoming `IAsyncDisposable` in .net core 3.0 if you absolutely need it. – Martin Ullrich May 31 '19 at 07:40
  • @MartinUllrich Using those methods may create a deadlock in certain circumstances. This is quite often the case when running tests with xunit. – betabandido Jun 05 '19 at 19:14

3 Answers3

9

It turns out xunit actually includes some support to handle the problem I was facing. Test classes can implement IAsyncLifetime to initialize and tear down tests in an asynchronous way. The interface looks like:

public interface IAsyncLifetime
{
    Task InitializeAsync();
    Task DisposeAsync();
}

While this is the solution to my specific problem, it does not solve the more generic problem of calling an asynchronous method from Dispose (none of the current answers do that either). I suppose for that we will need to wait until IAsyncDisposable is available in .NET core 3.0 (thanks @MartinUllrich for this info).

betabandido
  • 18,946
  • 11
  • 62
  • 76
4

I have similar problems, especially XUnit is a problem child here. I "solved" that with moving all cleanup code into the test, e.g. a try..finally block. It's less elegant, but works more stable and avoid async dispose. If you have a lot of tests, you could add a method which reduces the boilerplate.

For example:

        private async Task WithFinalizer(Action<Task> toExecute)
    {

        try
        {
            await toExecute();
        }
        finally
        {
           // cleanup here
        }
    }

    // Usage
    [Fact]
    public async Task TestIt()
    {
        await WithFinalizer(async =>
        {
         // your test
         });
    }

Another benefit of this is that, in my experience, cleanup is often highly dependent of the test - providing a custom finalizer for each test is much easier with this technique (add a second action which can be used as a finalizer)

Christian Sauer
  • 10,351
  • 10
  • 53
  • 85
  • I just hoped it would be possible to avoid all this boilerplate code. I suppose it is actually quite neat. But, I just found out xunit provides some support to deal with the issue I was facing :) – betabandido Jun 05 '19 at 19:19
0

A test class performs several tests that relate to each other. A test class usually tests a class, or a group of classes that closely work together. Sometimes a test class tests only one function.

Usually the tests should be designed such that they don't depend on other tests: Test A should succeed without having to run Test B, and the other way round: Tests may not assume anything about other tests.

Usually a test creates some precondition, calls the function being tested and checks whether the postcondition is met. Therefore every test usually creates its own environment.

If a bunch of tests needs a similar environment, to save test-time, it is quite common to create the environment once for all these tests, run the tests, and dispose the environment. This is what you do in your test class.

However, if in one of your tests you create a task to call an async function, you should wait inside that test for the result of that task. If you don't, you can't test whether the async function does what it is meant to do, namely: "Create a Task that, when awaited for, returns ...".

void TestA()
{
    Task taskA = null;
    try
    {
        // start a task without awaiting
        taskA = DoSomethingAsync();
        // perform your test
        ...
        // wait until taskA completes
        taskA.Wait();
        // check the result of taskA
        ...
     }
     catch (Exception exc)
     {
         ...
     }
     finally
     {
         // make sure that even if exception TaskA completes
         taskA.Wait();
     }
 }

Conclusion: every Test method that creates a task should await for this class to complete before finishing

In rare occasions you don't to wait for the Task to complete before your test finishes. Maybe to see what happens if you don't await for the task. I still think that is a strange idea, because this might influence the other tests, but hey, it's your test class.

This means, that your Dispose does have to make sure that all started tasks that were not finished when the test method ended are awaited for.

List<Task> nonAwaitedTasks = new List<Task>();

var TestA()
{
    // start a Task, for some reason you don't want to await for it:
    Task taskA = DoSomethingAsync(...);
    // perform your test

    // finish without awaiting for taskA. Make sure it will be awaited before the
    // class is disposed:
    nonAwaitedTasks.Add(taskA);
}

public void Dispose()
{
    Dispose(true);
}
protected void Dispose(bool disposing)
{
    if (disposing)
    {
        // wait for all tasks to complete
        Task.WaitAll(this.nonAwaitedTasks);
    }
}
}
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116