Skip to content

Commit e64ce7f

Browse files
committed
Introduce ServiceProviderLifetimeType for scoped dependency container lifetimes and refactor related logic in ServiceCollectionFinder.
1 parent fff8ef9 commit e64ce7f

5 files changed

Lines changed: 198 additions & 46 deletions

File tree

Plugins/Reqnroll.Microsoft.Extensions.DependencyInjection.ReqnrollPlugin/DependencyInjectionPlugin.cs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class DependencyInjectionPlugin : IRuntimePlugin
2929
public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters, UnitTestProviderConfiguration unitTestProviderConfiguration)
3030
{
3131
runtimePluginEvents.CustomizeGlobalDependencies += CustomizeGlobalDependencies;
32+
runtimePluginEvents.CustomizeTestThreadDependencies += CustomizeTestThreadDependenciesEventHandler;
3233
runtimePluginEvents.CustomizeFeatureDependencies += CustomizeFeatureDependenciesEventHandler;
3334
runtimePluginEvents.CustomizeScenarioDependencies += CustomizeScenarioDependenciesEventHandler;
3435
}
@@ -45,21 +46,15 @@ private void CustomizeGlobalDependencies(object sender, CustomizeGlobalDependenc
4546
args.ObjectContainer.RegisterTypeAs<ServiceCollectionFinder, IServiceCollectionFinder>();
4647
}
4748

48-
// We store the (MS) service provider in the global (BoDi) container, we create it only once.
49-
// It must be lazy (hence factory) because at this point we still don't have the bindings mapped.
50-
args.ObjectContainer.RegisterFactoryAs<RootServiceProviderContainer>(() =>
51-
{
52-
var serviceCollectionFinder = args.ObjectContainer.Resolve<IServiceCollectionFinder>();
53-
var (services, scoping) = serviceCollectionFinder.GetServiceCollection();
54-
55-
RegisterProxyBindings(args.ObjectContainer, services);
56-
return new RootServiceProviderContainer(services.BuildServiceProvider(), scoping);
57-
});
49+
var serviceCollectionFinder = args.ObjectContainer.Resolve<IServiceCollectionFinder>();
50+
var lifetime = serviceCollectionFinder.GetServiceProviderLifetime();
5851

59-
args.ObjectContainer.RegisterFactoryAs<IServiceProvider>(() =>
52+
if (lifetime == ServiceProviderLifetimeType.Global)
6053
{
61-
return args.ObjectContainer.Resolve<RootServiceProviderContainer>().ServiceProvider;
62-
});
54+
args.ObjectContainer.RegisterFactoryAs<RootServiceProviderContainer>(() => BuildRootServiceProviderContainer(args.ObjectContainer));
55+
}
56+
57+
args.ObjectContainer.RegisterFactoryAs<IServiceProvider>(c => c.Resolve<RootServiceProviderContainer>().ServiceProvider);
6358

6459
// Will make sure DI scope is disposed.
6560
var lcEvents = args.ObjectContainer.Resolve<RuntimePluginTestExecutionLifecycleEvents>();
@@ -70,8 +65,27 @@ private void CustomizeGlobalDependencies(object sender, CustomizeGlobalDependenc
7065
}
7166
}
7267

68+
private static void CustomizeTestThreadDependenciesEventHandler(object sender, CustomizeTestThreadDependenciesEventArgs args)
69+
{
70+
var serviceCollectionFinder = args.ObjectContainer.Resolve<IServiceCollectionFinder>();
71+
var lifetime = serviceCollectionFinder.GetServiceProviderLifetime();
72+
73+
if (lifetime == ServiceProviderLifetimeType.TestThread)
74+
{
75+
args.ObjectContainer.RegisterFactoryAs<RootServiceProviderContainer>(() => BuildRootServiceProviderContainer(args.ObjectContainer));
76+
}
77+
}
78+
7379
private static void CustomizeFeatureDependenciesEventHandler(object sender, CustomizeFeatureDependenciesEventArgs args)
7480
{
81+
var serviceCollectionFinder = args.ObjectContainer.Resolve<IServiceCollectionFinder>();
82+
var lifetime = serviceCollectionFinder.GetServiceProviderLifetime();
83+
84+
if (lifetime == ServiceProviderLifetimeType.Feature)
85+
{
86+
args.ObjectContainer.RegisterFactoryAs<RootServiceProviderContainer>(() => BuildRootServiceProviderContainer(args.ObjectContainer));
87+
}
88+
7589
// At this point we have the bindings, we can resolve the service provider, which will build it if it's the first time.
7690
var spContainer = args.ObjectContainer.Resolve<RootServiceProviderContainer>();
7791

@@ -101,6 +115,14 @@ private static void AfterFeaturePluginLifecycleEventHandler(object sender, Runti
101115

102116
private static void CustomizeScenarioDependenciesEventHandler(object sender, CustomizeScenarioDependenciesEventArgs args)
103117
{
118+
var serviceCollectionFinder = args.ObjectContainer.Resolve<IServiceCollectionFinder>();
119+
var lifetime = serviceCollectionFinder.GetServiceProviderLifetime();
120+
121+
if (lifetime == ServiceProviderLifetimeType.Scenario)
122+
{
123+
args.ObjectContainer.RegisterFactoryAs<RootServiceProviderContainer>(() => BuildRootServiceProviderContainer(args.ObjectContainer));
124+
}
125+
104126
// At this point we have the bindings, we can resolve the service provider, which will build it if it's the first time.
105127
var spContainer = args.ObjectContainer.Resolve<RootServiceProviderContainer>();
106128

@@ -127,6 +149,15 @@ private static void AfterScenarioPluginLifecycleEventHandler(object sender, Runt
127149
}
128150
}
129151

152+
private static RootServiceProviderContainer BuildRootServiceProviderContainer(IObjectContainer container)
153+
{
154+
var serviceCollectionFinder = container.Resolve<IServiceCollectionFinder>();
155+
var (services, scoping) = serviceCollectionFinder.GetServiceCollection();
156+
157+
RegisterProxyBindings(container, services);
158+
return new RootServiceProviderContainer(services.BuildServiceProvider(), scoping);
159+
}
160+
130161
private static void RegisterProxyBindings(IObjectContainer objectContainer, IServiceCollection services)
131162
{
132163
// Required for DI of binding classes that want container injections

Plugins/Reqnroll.Microsoft.Extensions.DependencyInjection.ReqnrollPlugin/IServiceCollectionFinder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Reqnroll.Microsoft.Extensions.DependencyInjection
44
{
55
public interface IServiceCollectionFinder
66
{
7+
ServiceProviderLifetimeType GetServiceProviderLifetime();
78
(IServiceCollection, ScopeLevelType) GetServiceCollection();
89
}
910
}

Plugins/Reqnroll.Microsoft.Extensions.DependencyInjection.ReqnrollPlugin/ScenarioDependenciesAttribute.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,26 @@ public enum ScopeLevelType
1414
Feature
1515
}
1616

17+
public enum ServiceProviderLifetimeType
18+
{
19+
/// <summary>
20+
/// Global lifetime. The container is created once for the entire test run.
21+
/// </summary>
22+
Global,
23+
/// <summary>
24+
/// Test thread lifetime. The container is created once for each test thread.
25+
/// </summary>
26+
TestThread,
27+
/// <summary>
28+
/// Feature lifetime. The container is created once for each feature.
29+
/// </summary>
30+
Feature,
31+
/// <summary>
32+
/// Scenario lifetime. The container is created once for each scenario.
33+
/// </summary>
34+
Scenario
35+
}
36+
1737
[AttributeUsage(AttributeTargets.Method)]
1838
public class ScenarioDependenciesAttribute : Attribute
1939
{
@@ -26,5 +46,10 @@ public class ScenarioDependenciesAttribute : Attribute
2646
/// Define when to create and destroy scope.
2747
/// </summary>
2848
public ScopeLevelType ScopeLevel { get; set; } = ScopeLevelType.Scenario;
49+
50+
/// <summary>
51+
/// Define the lifetime of the Service Provider instance.
52+
/// </summary>
53+
public ServiceProviderLifetimeType ServiceProviderLifetime { get; set; } = ServiceProviderLifetimeType.Global;
2954
}
3055
}

Plugins/Reqnroll.Microsoft.Extensions.DependencyInjection.ReqnrollPlugin/ServiceCollectionFinder.cs

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,58 @@
33
using System.Linq;
44
using System.Reflection;
55
using Microsoft.Extensions.DependencyInjection;
6+
using Reqnroll.Bindings.Discovery;
7+
using Reqnroll.Infrastructure;
68

79
namespace Reqnroll.Microsoft.Extensions.DependencyInjection
810
{
911
public class ServiceCollectionFinder : IServiceCollectionFinder
1012
{
11-
private readonly ITestRunnerManager testRunnerManager;
12-
private (IServiceCollection, ScopeLevelType) _cache;
13+
private readonly ITestRunnerManager _testRunnerManager;
14+
private readonly IRuntimeBindingRegistryBuilder _bindingRegistryBuilder;
15+
private readonly ITestAssemblyProvider _testAssemblyProvider;
16+
private (IServiceCollection ServiceCollection, ScenarioDependenciesAttribute Attribute) _cache;
1317

14-
public ServiceCollectionFinder(ITestRunnerManager testRunnerManager)
18+
public ServiceCollectionFinder(ITestRunnerManager testRunnerManager, IRuntimeBindingRegistryBuilder bindingRegistryBuilder, ITestAssemblyProvider testAssemblyProvider, IBindingAssemblyLoader bindingAssemblyLoader)
1519
{
16-
this.testRunnerManager = testRunnerManager;
20+
_testRunnerManager = testRunnerManager;
21+
_bindingRegistryBuilder = bindingRegistryBuilder;
22+
_testAssemblyProvider = testAssemblyProvider;
23+
}
24+
25+
public ServiceProviderLifetimeType GetServiceProviderLifetime()
26+
{
27+
if (_cache == default)
28+
{
29+
PopulateCache();
30+
}
31+
32+
return _cache.Attribute.ServiceProviderLifetime;
1733
}
1834

1935
public (IServiceCollection, ScopeLevelType) GetServiceCollection()
36+
{
37+
if (_cache == default)
38+
{
39+
PopulateCache();
40+
}
41+
42+
return (_cache.ServiceCollection, _cache.Attribute.ScopeLevel);
43+
}
44+
45+
private void PopulateCache()
2046
{
2147
if (_cache != default)
2248
{
23-
return _cache;
49+
return;
2450
}
2551

26-
var assemblies = testRunnerManager.BindingAssemblies;
52+
var assemblies = _testRunnerManager.BindingAssemblies ?? _bindingRegistryBuilder.GetBindingAssemblies(_testAssemblyProvider.TestAssembly);
2753
foreach (var assembly in assemblies)
2854
{
2955
foreach (var type in assembly.GetTypes())
3056
{
31-
foreach (MethodInfo methodInfo in type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
57+
foreach (var methodInfo in type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
3258
{
3359
var scenarioDependenciesAttribute = (ScenarioDependenciesAttribute)Attribute.GetCustomAttribute(methodInfo, typeof(ScenarioDependenciesAttribute));
3460

@@ -39,7 +65,8 @@ public ServiceCollectionFinder(ITestRunnerManager testRunnerManager)
3965
{
4066
AddBindingAttributes(assemblies, serviceCollection);
4167
}
42-
return _cache = (serviceCollection, scenarioDependenciesAttribute.ScopeLevel);
68+
_cache = (serviceCollection, scenarioDependenciesAttribute);
69+
return;
4370
}
4471
}
4572
}
@@ -50,12 +77,13 @@ public ServiceCollectionFinder(ITestRunnerManager testRunnerManager)
5077

5178
private static IServiceCollection GetServiceCollection(MethodInfo methodInfo)
5279
{
53-
var serviceCollection = methodInfo.Invoke(null, null);
54-
if(methodInfo.ReturnType == typeof(void))
80+
if (methodInfo.ReturnType == typeof(void))
5581
{
5682
throw new InvalidScenarioDependenciesException("the method doesn't return a value.");
5783
}
5884

85+
var serviceCollection = methodInfo.Invoke(null, null);
86+
5987
if (serviceCollection == null)
6088
{
6189
throw new InvalidScenarioDependenciesException("returned null.");

0 commit comments

Comments
 (0)