diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacPlugin.cs b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacPlugin.cs index 68c8b3073..1e1555455 100644 --- a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacPlugin.cs +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacPlugin.cs @@ -60,10 +60,26 @@ public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginPar runtimePluginEvents.CustomizeFeatureDependencies += (sender, args) => { - if (args.ObjectContainer.BaseContainer.IsRegistered()) + var containerBuilderFinder = args.ObjectContainer.Resolve(); + + var featureScopeFinder = containerBuilderFinder.GetFeatureLifetimeScope(); + + ILifetimeScope featureScope = null; + + if (featureScopeFinder != null) + { + featureScope = featureScopeFinder(); + } + else if (args.ObjectContainer.BaseContainer.IsRegistered()) + { + var testThreadScope = args.ObjectContainer.BaseContainer.Resolve(); + + featureScope = testThreadScope.BeginLifetimeScope(nameof(FeatureContext)); + } + + if (featureScope != null) { - var container = args.ObjectContainer.BaseContainer.Resolve(); - args.ObjectContainer.RegisterFactoryAs(() => container.BeginLifetimeScope(nameof(FeatureContext))); + args.ObjectContainer.RegisterInstanceAs(featureScope); } }; @@ -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); }); } @@ -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() && configureScenarioContainer != null) + if (objectContainer.BaseContainer.IsRegistered()) { return objectContainer.BaseContainer.Resolve(); } + var configureScenarioContainer = containerBuilderFinder.GetConfigureScenarioContainer(); + if (configureScenarioContainer != null) { var containerBuilder = new global::Autofac.ContainerBuilder(); diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacTestObjectResolver.cs b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacTestObjectResolver.cs index 4b14c1d74..336befafc 100644 --- a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacTestObjectResolver.cs +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacTestObjectResolver.cs @@ -20,6 +20,7 @@ public object ResolveBindingInstance(Type bindingType, IObjectContainer containe var lifeTimeScope = container.Resolve(); return lifeTimeScope.Resolve(bindingType); } + return container.Resolve(bindingType); } } diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/ContainerBuilderFinder.cs b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/ContainerBuilderFinder.cs index 2bc5d2de7..15212ebd3 100644 --- a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/ContainerBuilderFinder.cs +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/ContainerBuilderFinder.cs @@ -10,7 +10,6 @@ namespace SpecFlow.Autofac { - public class ContainerBuilderFinder : IContainerBuilderFinder { private readonly IBindingRegistry bindingRegistry; @@ -19,6 +18,7 @@ public class ContainerBuilderFinder : IContainerBuilderFinder private readonly Lazy> createConfigureGlobalContainer; private readonly Lazy> createConfigureScenarioContainer; private readonly Lazy> createScenarioContainerBuilder; + private readonly Lazy> getFeatureLifetimeScope; public ContainerBuilderFinder(IBindingRegistry bindingRegistry, IRuntimeBindingRegistryBuilder bindingRegistryBuilder, ITestAssemblyProvider testAssemblyProvider) { @@ -33,6 +33,7 @@ static ContainerBuilder invokeVoidAndReturnBuilder(ContainerBuilder containerBui createConfigureGlobalContainer = new Lazy>(() => FindCreateScenarioContainerBuilder(typeof(GlobalDependenciesAttribute), typeof(void), invokeVoidAndReturnBuilder), true); createConfigureScenarioContainer = new Lazy>(() => FindCreateScenarioContainerBuilder(typeof(ScenarioDependenciesAttribute), typeof(void), invokeVoidAndReturnBuilder), true); createScenarioContainerBuilder = new Lazy>(() => FindCreateScenarioContainerBuilder(typeof(ScenarioDependenciesAttribute), typeof(ContainerBuilder), (c, m) => (ContainerBuilder)m.Invoke(null, null)), true); + getFeatureLifetimeScope = new Lazy>(() => FindLifetimeScope(typeof(FeatureDependenciesAttribute), typeof(ILifetimeScope), m => (ILifetimeScope)m.Invoke(null, null))); } public Func GetConfigureGlobalContainer() @@ -51,30 +52,36 @@ public Func GetCreateScenarioContainerBuilde return createScenarioContainerBuilder.Value; } + public Func GetFeatureLifetimeScope() + { + return getFeatureLifetimeScope.Value; + } + + protected virtual Func FindLifetimeScope(Type attributeType, Type returnType, Func invoke) + { + var method = GetMethod(attributeType, returnType); + + return method == null + ? null + : () => invoke(method); + } + protected virtual Func FindCreateScenarioContainerBuilder(Type attributeType, Type returnType, Func 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) 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)); } } } \ No newline at end of file diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/FeatureDependenciesAttribute.cs b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/FeatureDependenciesAttribute.cs new file mode 100644 index 000000000..eff47ecf0 --- /dev/null +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/FeatureDependenciesAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace SpecFlow.Autofac +{ + [AttributeUsage(AttributeTargets.Method)] + public class FeatureDependenciesAttribute : Attribute + { + } +} diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/IContainerBuilderFinder.cs b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/IContainerBuilderFinder.cs index 0e914a1ec..da2c8b41a 100644 --- a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/IContainerBuilderFinder.cs +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/IContainerBuilderFinder.cs @@ -10,5 +10,7 @@ public interface IContainerBuilderFinder Func GetConfigureGlobalContainer(); Func GetCreateScenarioContainerBuilder(); + + Func GetFeatureLifetimeScope(); } } \ No newline at end of file diff --git a/docs/Integrations/Autofac.md b/docs/Integrations/Autofac.md index 68dee41f2..385bdf7bc 100644 --- a/docs/Integrations/Autofac.md +++ b/docs/Integrations/Autofac.md @@ -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(); +} +```