Skip to content

Commit

Permalink
Allow Autofac plugin to provide existing lifetime scope (#2622)
Browse files Browse the repository at this point in the history
  • Loading branch information
robertcoltheart authored Aug 22, 2022
1 parent 77d8ad7 commit d8e0c3f
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 27 deletions.
36 changes: 30 additions & 6 deletions Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,26 @@ public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginPar

runtimePluginEvents.CustomizeFeatureDependencies += (sender, args) =>
{
if (args.ObjectContainer.BaseContainer.IsRegistered<ILifetimeScope>())
var containerBuilderFinder = args.ObjectContainer.Resolve<IContainerBuilderFinder>();
var featureScopeFinder = containerBuilderFinder.GetFeatureLifetimeScope();
ILifetimeScope featureScope = null;
if (featureScopeFinder != null)
{
featureScope = featureScopeFinder();
}
else if (args.ObjectContainer.BaseContainer.IsRegistered<ILifetimeScope>())
{
var testThreadScope = args.ObjectContainer.BaseContainer.Resolve<ILifetimeScope>();
featureScope = testThreadScope.BeginLifetimeScope(nameof(FeatureContext));
}
if (featureScope != null)
{
var container = args.ObjectContainer.BaseContainer.Resolve<ILifetimeScope>();
args.ObjectContainer.RegisterFactoryAs(() => container.BeginLifetimeScope(nameof(FeatureContext)));
args.ObjectContainer.RegisterInstanceAs(featureScope);
}
};

Expand All @@ -80,7 +96,13 @@ public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginPar
return featureScope.BeginLifetimeScope(nameof(ScenarioContext), containerBuilder =>
{
var configureScenarioContainer = containerBuilderFinder.GetConfigureScenarioContainer();
RegisterSpecflowDependecies(args.ObjectContainer, configureScenarioContainer(containerBuilder));
if (configureScenarioContainer != null)
{
containerBuilder = configureScenarioContainer(containerBuilder);
}
RegisterSpecflowDependecies(args.ObjectContainer, containerBuilder);
});
}
Expand All @@ -97,14 +119,16 @@ public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginPar
});
};
}

private static ILifetimeScope GetFeatureScope(ObjectContainer objectContainer, IContainerBuilderFinder containerBuilderFinder)
{
var configureScenarioContainer = containerBuilderFinder.GetConfigureScenarioContainer();
if (objectContainer.BaseContainer.IsRegistered<ILifetimeScope>() && configureScenarioContainer != null)
if (objectContainer.BaseContainer.IsRegistered<ILifetimeScope>())
{
return objectContainer.BaseContainer.Resolve<ILifetimeScope>();
}

var configureScenarioContainer = containerBuilderFinder.GetConfigureScenarioContainer();

if (configureScenarioContainer != null)
{
var containerBuilder = new global::Autofac.ContainerBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public object ResolveBindingInstance(Type bindingType, IObjectContainer containe
var lifeTimeScope = container.Resolve<ILifetimeScope>();
return lifeTimeScope.Resolve(bindingType);
}

return container.Resolve(bindingType);
}
}
Expand Down
49 changes: 28 additions & 21 deletions Plugins/SpecFlow.Autofac.SpecFlowPlugin/ContainerBuilderFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

namespace SpecFlow.Autofac
{

public class ContainerBuilderFinder : IContainerBuilderFinder
{
private readonly IBindingRegistry bindingRegistry;
Expand All @@ -19,6 +18,7 @@ public class ContainerBuilderFinder : IContainerBuilderFinder
private readonly Lazy<Func<ContainerBuilder, ContainerBuilder>> createConfigureGlobalContainer;
private readonly Lazy<Func<ContainerBuilder, ContainerBuilder>> createConfigureScenarioContainer;
private readonly Lazy<Func<ContainerBuilder, ContainerBuilder>> createScenarioContainerBuilder;
private readonly Lazy<Func<ILifetimeScope>> getFeatureLifetimeScope;

public ContainerBuilderFinder(IBindingRegistry bindingRegistry, IRuntimeBindingRegistryBuilder bindingRegistryBuilder, ITestAssemblyProvider testAssemblyProvider)
{
Expand All @@ -33,6 +33,7 @@ static ContainerBuilder invokeVoidAndReturnBuilder(ContainerBuilder containerBui
createConfigureGlobalContainer = new Lazy<Func<ContainerBuilder, ContainerBuilder>>(() => FindCreateScenarioContainerBuilder(typeof(GlobalDependenciesAttribute), typeof(void), invokeVoidAndReturnBuilder), true);
createConfigureScenarioContainer = new Lazy<Func<ContainerBuilder, ContainerBuilder>>(() => FindCreateScenarioContainerBuilder(typeof(ScenarioDependenciesAttribute), typeof(void), invokeVoidAndReturnBuilder), true);
createScenarioContainerBuilder = new Lazy<Func<ContainerBuilder, ContainerBuilder>>(() => FindCreateScenarioContainerBuilder(typeof(ScenarioDependenciesAttribute), typeof(ContainerBuilder), (c, m) => (ContainerBuilder)m.Invoke(null, null)), true);
getFeatureLifetimeScope = new Lazy<Func<ILifetimeScope>>(() => FindLifetimeScope(typeof(FeatureDependenciesAttribute), typeof(ILifetimeScope), m => (ILifetimeScope)m.Invoke(null, null)));
}

public Func<ContainerBuilder, ContainerBuilder> GetConfigureGlobalContainer()
Expand All @@ -51,30 +52,36 @@ public Func<ContainerBuilder, ContainerBuilder> GetCreateScenarioContainerBuilde
return createScenarioContainerBuilder.Value;
}

public Func<ILifetimeScope> GetFeatureLifetimeScope()
{
return getFeatureLifetimeScope.Value;
}

protected virtual Func<ILifetimeScope> FindLifetimeScope(Type attributeType, Type returnType, Func<MethodInfo, ILifetimeScope> invoke)
{
var method = GetMethod(attributeType, returnType);

return method == null
? null
: () => invoke(method);
}

protected virtual Func<ContainerBuilder, ContainerBuilder> FindCreateScenarioContainerBuilder(Type attributeType, Type returnType, Func<ContainerBuilder, MethodInfo, ContainerBuilder> invoke)
{
var assemblies = bindingRegistry.GetBindingAssemblies();
foreach (var assembly in assemblies)
{
foreach (var type in assembly.GetTypes())
{
foreach (var methodInfo in type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).Where(m => Attribute.IsDefined(m, attributeType)))
{
if (methodInfo.ReturnType == returnType)
{
return (containerBuilder) => invoke(containerBuilder, methodInfo);
}
}
}
}
var method = GetMethod(attributeType, returnType);

return null;
return method == null
? null
: containerBuilder => invoke(containerBuilder, method);
}

//return (assemblies
// .SelectMany(assembly => assembly.GetTypes(), (_, type) => type)
// .SelectMany(
// type => type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).Where(m => Attribute.IsDefined(m, typeof (ScenarioDependenciesAttribute))),
// (_, methodInfo) => (Func<ContainerBuilder>) (() => (ContainerBuilder) methodInfo.Invoke(null, null)))).FirstOrDefault();
private MethodInfo GetMethod(Type attributeType, Type returnType)
{
return bindingRegistry.GetBindingAssemblies()
.SelectMany(x => x.GetTypes())
.SelectMany(x => x.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
.Where(x => x.ReturnType == returnType)
.FirstOrDefault(x => Attribute.IsDefined(x, attributeType));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace SpecFlow.Autofac
{
[AttributeUsage(AttributeTargets.Method)]
public class FeatureDependenciesAttribute : Attribute
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ public interface IContainerBuilderFinder
Func<ContainerBuilder, ContainerBuilder> GetConfigureGlobalContainer();

Func<ContainerBuilder, ContainerBuilder> GetCreateScenarioContainerBuilder();

Func<ILifetimeScope> GetFeatureLifetimeScope();
}
}
22 changes: 22 additions & 0 deletions docs/Integrations/Autofac.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,26 @@ public static void CreateContainerBuilder(ContainerBuilder containerBuilder)
(Recommended to put it into the `Support` folder) that returns an Autofac `ContainerBuilder` and tag it with the `[ScenarioDependencies]` attribute.


### 5. If you have an existing container, built and owned by your application under test, you can use that instead of letting SpecFlow manage your container
Create a static method in your SpecFlow project to return a lifetime scope from your container. Note that SpecFlow creates a second scope under yours,
so be sure to pair this use-case with the `CreateContainerBuilder` method above to add your step bindings.

```csharp
[FeatureDependencies]
public static ILifetimeScope GetFeatureLifetimeScope()
{
// TODO: Add any top-level dependencies here, though note that usually step bindings
// should be declared in the Configure method below, as this will ensure they
// are in the correct scope to inject ScenarioContext etc.
return containerScope.BeginLifetimeScope();
}

[ScenarioDependencies]
public static void ConfigureContainerBuilder(ContainerBuilder containerBuilder)
{
//TODO: add customizations, stubs required for testing
containerBuilder.AddSpecFlowBindings<TestDependencies>();
}
```

0 comments on commit d8e0c3f

Please sign in to comment.