Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for IAsyncLifetime #5

Closed
vabic opened this issue Mar 24, 2021 · 2 comments · Fixed by #6
Closed

Support for IAsyncLifetime #5

vabic opened this issue Mar 24, 2021 · 2 comments · Fixed by #6

Comments

@vabic
Copy link

vabic commented Mar 24, 2021

Works well with standard workflow , i.e. Fixture with constructor.
When use with a Fixture who implement IAsyncLifetime then InitializeAsync is never trigged.

Any solution for supporting IAsyncLifetime ?

.Net core 3.1

best regards.

@JDCain
Copy link
Owner

JDCain commented Nov 30, 2021

Sorry for the delay, I missed this.

I am using IAsyncLifeTime in some of my test fixtures to load and unload items and it is working for me. Though I am implementing both IAsyncLifetime & IAsyncDisposable.

async Task IAsyncLifetime.DisposeAsync()
{
    await DisposeAsync();
}
public async ValueTask DisposeAsync()
{
    //do stuff
}

If this is still not working for you can you upload a example project with it not working which I can test with?

@candoumbe
Copy link
Contributor

candoumbe commented Dec 5, 2021

Hi !
I just found this repo while looking for a way to perform database migrations before running integration tests.
I'm working on a "side" project mainly to try new ideas.

Long story short,
for this specific issue, it seems that when inheriting XunitTestAssemblyRunner, one could override the AfterTestAssemblyStartingAsync and discover all the fixture classes that implement IAsyncLifetime for instance and call their specific InitializeAsync methods.

Also the library could find all fixtures implementing IDisposable / AsyncDisposable and call their Dispose (resp. DisposeAsync) method.

public class TestAssemblyRunner : XunitTestAssemblyRunner
    {
        private readonly IDictionary<Type, object> _assemblyFixtureMappings = new Dictionary<Type, object>();

        public TestAssemblyRunner(
            ITestAssembly testAssembly,
            IEnumerable<IXunitTestCase> testCases,
            IMessageSink diagnosticMessageSink,
            IMessageSink executionMessageSink,
            ITestFrameworkExecutionOptions executionOptions)
            : base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions)
        { }

        protected override async Task AfterTestAssemblyStartingAsync()
        {
            // Let everything initialize
            await base.AfterTestAssemblyStartingAsync().ConfigureAwait(false);

            // Go find all the AssemblyFixtureAttributes adorned on the test assembly
            await Aggregator.RunAsync(async () =>
            {
                var fixturesAttributes = ((IReflectionAssemblyInfo)TestAssembly.Assembly).Assembly
                    .GetCustomAttributes(typeof(AssemblyFixtureAttribute), false)
                    .Cast<AssemblyFixtureAttribute>()
                    .ToArray();

                // Instantiate all the fixtures
                foreach (AssemblyFixtureAttribute fixtureAttribute in fixturesAttributes)
                {
                    var hasConstructorWithMessageSink = fixtureAttribute.FixtureType.GetConstructor(new[] { typeof(IMessageSink) }) != null;
                    _assemblyFixtureMappings[fixtureAttribute.FixtureType] = hasConstructorWithMessageSink
                        ? Activator.CreateInstance(fixtureAttribute.FixtureType, ExecutionMessageSink)
                        : Activator.CreateInstance(fixtureAttribute.FixtureType);
                }

                // Initialize IAsyncLifetime fixtures
                foreach (IAsyncLifetime asyncLifetime in _assemblyFixtureMappings.Values.OfType<IAsyncLifetime>())
                {
                    await asyncLifetime.InitializeAsync().ConfigureAwait(false);
                }
            }).ConfigureAwait(false);
        }

        protected override async Task BeforeTestAssemblyFinishedAsync()
        {
            // Make sure we clean up everybody who is disposable, and use Aggregator.Run to isolate Dispose failures
            foreach (IDisposable disposable in _assemblyFixtureMappings.Values.OfType<IDisposable>())
            {
                Aggregator.Run(disposable.Dispose);
            }

            foreach (IAsyncDisposable disposable in _assemblyFixtureMappings.Values.OfType<IAsyncDisposable>())
            {
                await Aggregator.RunAsync(async () => await disposable.DisposeAsync().ConfigureAwait(false)).ConfigureAwait(false);
            }

            foreach (IAsyncLifetime disposable in _assemblyFixtureMappings.Values.OfType<IAsyncLifetime>())
            {
                await Aggregator.RunAsync(disposable.DisposeAsync).ConfigureAwait(false);
            }

            await base.BeforeTestAssemblyFinishedAsync().ConfigureAwait(false);
        }

        protected override async Task<RunSummary> RunTestCollectionAsync(
            IMessageBus messageBus,
            ITestCollection testCollection,
            IEnumerable<IXunitTestCase> testCases,
            CancellationTokenSource cancellationTokenSource)
            => await new XUnitTestCollectionRunnerWithAssemblyFixture(
                _assemblyFixtureMappings,
                testCollection,
                testCases,
                DiagnosticMessageSink,
                messageBus,
                TestCaseOrderer,
                new ExceptionAggregator(Aggregator),
                cancellationTokenSource)
            .RunAsync(cancellationTokenSource.Token);
    }

This code needs some adjustments as the library provides a IAssemblyFixture<T> interface (vs AssemblyFixtureAttribute) but overall I think it's something doable.

I can make a PR if it's ok for you ?

Regards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants