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

Add IAssemblyAsyncLifetime interface #30

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,40 @@ public class MyDatabaseTests : IAssemblyFixture<DatabaseFixture>
fixtures cannot take dependencies on other fixtures. If you have need to
control creation order and/or have dependencies between fixtures, you should
create a class which encapsulates the other two fixtures, so that it can
do the object creation itself.
do the object creation itself.

### IAssemblyAsyncLifetime

***When to use***: when you want to do some async actions on the test assembly
load and after the test assembly finished but no need to share anything to tests.
For example, you could create and remove some docker containers with databases
and so on.

To use IAssemblyAsyncLifetime, you need to take the following steps:

- Implement IAssemblyAsyncLifetime somewhere in the assembly.

Here is a simple example:

```csharp
public class MyAssemblyAsyncLifetime : IAssemblyAsyncLifetime
{
public async Task InitializeAsync()
{
// ... do some async actions here ...
}

public async Task DisposeAsync()
{
// ... do some async actions here ...
}
}

```

If you need multiple IAssemblyAsyncLifetime objects, you can implement the
interface as many times as you want.

Note that you cannot control the order that IAssemblyAsyncLifetime objects
are created, and IAssemblyAsyncLifetime cannot take dependencies on other
IAssemblyAsyncLifetime.
20 changes: 20 additions & 0 deletions src/AssemblyFixture/IAssemblyAsyncLifetime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Threading.Tasks;

namespace Xunit;

/// <summary>
/// Used to provide asynchronous lifetime functionality per assembly.
/// It is like xunit IAsyncLifetime, but per assembly
/// </summary>
public interface IAssemblyAsyncLifetime
{
/// <summary>
/// Called before tests started in the assembly.
/// </summary>
Task InitializeAsync();

/// <summary>
/// Called when all tests finished in the assembly.
/// </summary>
Task DisposeAsync();
}
34 changes: 32 additions & 2 deletions src/AssemblyFixture/TestAssemblyRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,44 @@ public TestAssemblyRunner(ITestAssembly testAssembly,
: base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions)
{
}

protected override async Task AfterTestAssemblyStartingAsync()
{
await base.AfterTestAssemblyStartingAsync();

// Go find all the IAssemblyAsyncLifetime adorned on the test assembly
Aggregator.Run(() =>
{
var type = typeof(IAssemblyAsyncLifetime);

var assemblyAsyncLifetimeClasses = ((IReflectionAssemblyInfo)TestAssembly.Assembly).Assembly
.GetTypes()
.Where(p => type.IsAssignableFrom(p));

// Instantiate all the classes implements IAssemblyAsyncLifetime
foreach (var assemblyAsyncLifetimeClass in assemblyAsyncLifetimeClasses)
assemblyFixtureMappings[assemblyAsyncLifetimeClass] =
Activator.CreateInstance(assemblyAsyncLifetimeClass)!;
});

// Call InitializeAsync on all instances of IAssemblyAsyncLifetime, and use Aggregator.RunAsync to isolate
// InitializeAsync failures
foreach (var disposable in assemblyFixtureMappings.Values.OfType<IAssemblyAsyncLifetime>())
await Aggregator.RunAsync(disposable.InitializeAsync);
}

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

// Call DisposeAsync on all instances of IAssemblyAsyncLifetime, and use Aggregator.RunAsync to isolate
// DisposeAsync failures
foreach (var disposable in assemblyFixtureMappings.Values.OfType<IAssemblyAsyncLifetime>())
await Aggregator.RunAsync(disposable.DisposeAsync);

return base.BeforeTestAssemblyFinishedAsync();
await base.BeforeTestAssemblyFinishedAsync();
}


Expand Down
26 changes: 26 additions & 0 deletions src/Tests/Samples.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;

namespace Xunit
{
Expand Down Expand Up @@ -34,6 +35,15 @@ public void EnsureSingleton()
Assert.Equal(1, MyAssemblyFixture.InstantiationCount);
}
}

public class Sample3
{
[Fact]
public void EnsureSingleton()
{
Assert.Equal(1, MyAssemblyAsyncLifetime.InitializationCount);
}
}

public class MyAssemblyFixture : IDisposable
{
Expand All @@ -51,4 +61,20 @@ public void Dispose()
//InstantiationCount = 0;
}
}

public class MyAssemblyAsyncLifetime : IAssemblyAsyncLifetime
{
public static int InitializationCount;

public Task InitializeAsync()
{
InitializationCount++;
return Task.CompletedTask;
}

public Task DisposeAsync()
{
return Task.CompletedTask;
}
}
}