Last active
November 19, 2024 06:20
-
-
Save jacob7395/cd0095c825e8ce229d5265df1527eac9 to your computer and use it in GitHub Desktop.
TUnit Dependency Injected Properties
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DependencyInjectionClassConstructor : DiClassConstructorBase | |
{ | |
public override T Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( | |
ClassConstructorMetadata classConstructorMetadata) where T : class | |
{ | |
IServiceProvider provider = CreateServiceProvider<T>(); | |
return ActivatorUtilities.GetServiceOrCreateInstance<T>(provider); | |
} | |
} | |
public abstract class DiClassConstructorBase : IClassConstructor, ILastTestInClassEventReceiver, ITestStartEventReceiver, | |
ILastTestInTestSessionEventReceiver | |
{ | |
private static readonly IMongoRunner Runner; | |
private static readonly ConcurrentDictionary<Type, ConcurrentBag<ServiceProvider>> ServiceProviders; | |
public abstract T Create<T>(ClassConstructorMetadata classConstructorMetadata) where T : class; | |
static DiClassConstructorBase() | |
{ | |
Runner = MongoRunner.Run(); | |
ServiceProviders = new(); | |
} | |
protected IEnumerable<Type> GetSubstitutedTypes<T>() | |
=> typeof(T).GetConstructors(BindingFlags.Public | BindingFlags.Instance) | |
.SelectMany(m => m.GetParameters()) | |
.Where(p => p.GetCustomAttribute<SubstituteInstanceAttribute>() is not null) | |
.Select(p => p.ParameterType) | |
.Distinct(); | |
public virtual async ValueTask OnTestStart(BeforeTestContext beforeTestContext) | |
{ | |
if (ServiceProviders.TryGetValue(beforeTestContext.TestDetails.ClassType, out var scope)) | |
{ | |
foreach (IMigrationsContext context in scope.SelectMany(s => s.GetServices<IMigrationsContext>())) | |
{ | |
await context.ApplyMigrationsAsync(); | |
} | |
} | |
} | |
public virtual async ValueTask IfLastTestInClass(ClassHookContext context, TestContext testContext) | |
{ | |
if (ServiceProviders.TryRemove(context.ClassType, out var serviceProviders)) | |
{ | |
foreach (ServiceProvider serviceProvider in serviceProviders) | |
{ | |
await serviceProvider.DisposeAsync(); | |
} | |
} | |
} | |
protected void AddAndRemoveType(IServiceCollection serviceCollection, Type interfaceType, object instance) | |
{ | |
RemoveType(serviceCollection, interfaceType); | |
serviceCollection.AddSingleton(interfaceType, instance); | |
} | |
private static void RemoveType(IServiceCollection serviceCollection, Type interfaceType) | |
{ | |
foreach (var serviceDescriptor in serviceCollection.Where(s => s.ServiceType == interfaceType).ToList()) | |
{ | |
serviceCollection.Remove(serviceDescriptor); | |
} | |
} | |
protected ServiceProvider CreateServiceProvider<T>() | |
{ | |
var serviceProvider = UserHubConfigurationHelper.BasicProvider( | |
collection => | |
{ | |
collection.AddCostServiceMediatR(); | |
ConfigureTestServices(collection); | |
AddAndRemoveType(collection, typeof(IMessageBus), Substitute.For<IMessageBus>()); | |
foreach (Type subType in GetSubstitutedTypes<T>()) | |
{ | |
AddAndRemoveType(collection, subType, Substitute.For([subType], [])); | |
} | |
RemoveType(collection, typeof(IMongoRunner)); | |
collection.AddSingleton<IMongoRunner>(_ => new EphemeralMongoRunnerTestWrapper(Runner)); | |
}); | |
ServiceProviders.AddOrUpdate( | |
typeof(T), | |
new ConcurrentBag<ServiceProvider>([serviceProvider]), | |
(_, bag) => | |
{ | |
bag.Add(serviceProvider); | |
return bag; | |
}); | |
return serviceProvider; | |
} | |
protected virtual IServiceCollection ConfigureTestServices(IServiceCollection serviceCollection) | |
{ | |
return serviceCollection; | |
} | |
// this is to try and clean up my local monog that gets spun up for tests, there are some issues with the process sticking around | |
public virtual async ValueTask IfLastTestInTestSession(TestSessionContext current, TestContext testContext) | |
{ | |
try | |
{ | |
string processName = "mongodb"; | |
var startInfo = new ProcessStartInfo | |
{ | |
FileName = "taskkill", | |
Arguments = $"/IM {processName}.exe /F", | |
RedirectStandardOutput = true, | |
UseShellExecute = false, | |
CreateNoWindow = true | |
}; | |
using Process? process = Process.Start(startInfo); | |
if (process is not null) | |
{ | |
await process.WaitForExitAsync(); | |
} | |
} | |
catch (Exception e) | |
{ | |
current.ErrorOutputWriter.WriteLine(e); | |
} | |
} | |
} | |
// Also have a version that is scoped the entire test class, but I don't use this anymore so might have some issues | |
public class ClassScopedDiClassConstructor : DiClassConstructorBase | |
{ | |
protected static readonly Lock ClassLock = Lock.Monitor(); | |
protected static readonly ConcurrentDictionary<Type, AsyncServiceScope> ClassScopedProviders = new(); | |
public IServiceProvider GetClassScopedProvider<T>() | |
{ | |
if (ClassScopedProviders.TryGetValue(typeof(T), out var scopes)) return scopes.ServiceProvider; | |
using (ClassLock.Acquire()) | |
{ | |
if (ClassScopedProviders.TryGetValue(typeof(T), out var newScopes)) return newScopes.ServiceProvider; | |
AsyncServiceScope serviceProvider = CreateServiceProvider<T>().CreateAsyncScope(); | |
return ClassScopedProviders.GetOrAdd(typeof(T), serviceProvider).ServiceProvider; | |
} | |
} | |
public override T Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( | |
ClassConstructorMetadata classConstructorMetadata) where T : class | |
{ | |
var provider = GetClassScopedProvider<T>(); | |
return ActivatorUtilities.GetServiceOrCreateInstance<T>(provider); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment