diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacPlugin.cs b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacPlugin.cs new file mode 100644 index 000000000..d2ccdb04c --- /dev/null +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacPlugin.cs @@ -0,0 +1,98 @@ +using System; +using Autofac; +using SpecFlow.Autofac; +using TechTalk.SpecFlow; +using TechTalk.SpecFlow.Infrastructure; +using TechTalk.SpecFlow.Plugins; +using TechTalk.SpecFlow.UnitTestProvider; + +[assembly: RuntimePlugin(typeof(AutofacPlugin))] + +namespace SpecFlow.Autofac +{ + using BoDi; + + using TechTalk.SpecFlow; + + public class AutofacPlugin : IRuntimePlugin + { + private static Object _registrationLock = new Object(); + + public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters, UnitTestProviderConfiguration unitTestProviderConfiguration) + { + runtimePluginEvents.CustomizeGlobalDependencies += (sender, args) => + { + // temporary fix for CustomizeGlobalDependencies called multiple times + // see https://github.com/techtalk/SpecFlow/issues/948 + if (!args.ObjectContainer.IsRegistered()) + { + // an extra lock to ensure that there are not two super fast threads re-registering the same stuff + lock (_registrationLock) + { + if (!args.ObjectContainer.IsRegistered()) + { + args.ObjectContainer.RegisterTypeAs(); + args.ObjectContainer.RegisterTypeAs(); + } + } + + // workaround for parallel execution issue - this should be rather a feature in BoDi? + args.ObjectContainer.Resolve(); + } + }; + + runtimePluginEvents.CustomizeScenarioDependencies += (sender, args) => + { + args.ObjectContainer.RegisterFactoryAs(() => + { + var containerBuilderFinder = args.ObjectContainer.Resolve(); + var createScenarioContainerBuilder = containerBuilderFinder.GetCreateScenarioContainerBuilder(); + var containerBuilder = createScenarioContainerBuilder(); + RegisterSpecflowDependecies(args.ObjectContainer, containerBuilder); + var container = containerBuilder.Build(); + return container.BeginLifetimeScope(); + }); + }; + } + + /// + /// Fix for https://github.com/gasparnagy/SpecFlow.Autofac/issues/11 Cannot resolve ScenarioInfo + /// Extracted from + /// https://github.com/techtalk/SpecFlow/blob/master/TechTalk.SpecFlow/Infrastructure/ITestObjectResolver.cs + /// The test objects might be dependent on particular SpecFlow infrastructure, therefore the implemented + /// resolution logic should support resolving the following objects (from the provided SpecFlow container): + /// , , and + /// (to be able to resolve any other SpecFlow infrastucture). So basically + /// the resolution of these classes has to be forwarded to the original container. + /// + /// SpecFlow DI container. + /// Autofac ContainerBuilder. + private void RegisterSpecflowDependecies( + IObjectContainer objectContainer, + global::Autofac.ContainerBuilder containerBuilder) + { + containerBuilder.Register(ctx => objectContainer).As(); + containerBuilder.Register( + ctx => + { + var specflowContainer = ctx.Resolve(); + var scenarioContext = specflowContainer.Resolve(); + return scenarioContext; + }).As(); + containerBuilder.Register( + ctx => + { + var specflowContainer = ctx.Resolve(); + var scenarioContext = specflowContainer.Resolve(); + return scenarioContext; + }).As(); + containerBuilder.Register( + ctx => + { + var specflowContainer = ctx.Resolve(); + var scenarioContext = specflowContainer.Resolve(); + return scenarioContext; + }).As(); + } + } +} diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacTestObjectResolver.cs b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacTestObjectResolver.cs new file mode 100644 index 000000000..c22d2c4b2 --- /dev/null +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/AutofacTestObjectResolver.cs @@ -0,0 +1,16 @@ +using System; +using Autofac; +using BoDi; +using TechTalk.SpecFlow.Infrastructure; + +namespace SpecFlow.Autofac +{ + public class AutofacTestObjectResolver : ITestObjectResolver + { + public object ResolveBindingInstance(Type bindingType, IObjectContainer scenarioContainer) + { + var componentContext = scenarioContainer.Resolve(); + return componentContext.Resolve(bindingType); + } + } +} diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/BindingRegistryExtensions.cs b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/BindingRegistryExtensions.cs new file mode 100644 index 000000000..d96abb72e --- /dev/null +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/BindingRegistryExtensions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using TechTalk.SpecFlow.Bindings; +using TechTalk.SpecFlow.Bindings.Reflection; + +namespace SpecFlow.Autofac +{ + public static class BindingRegistryExtensions + { + public static IEnumerable GetBindingTypes(this IBindingRegistry bindingRegistry) + { + return bindingRegistry.GetStepDefinitions().Cast() + .Concat(bindingRegistry.GetHooks().Cast()) + .Concat(bindingRegistry.GetStepTransformations()) + .Select(b => b.Method.Type) + .Distinct(); + } + + public static IEnumerable GetBindingAssemblies(this IBindingRegistry bindingRegistry) + { + return bindingRegistry.GetBindingTypes().OfType() + .Select(t => t.Type.Assembly) + .Distinct(); + } + } +} diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/ContainerBuilderFinder.cs b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/ContainerBuilderFinder.cs new file mode 100644 index 000000000..1537430b3 --- /dev/null +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/ContainerBuilderFinder.cs @@ -0,0 +1,50 @@ +using System; +using System.Linq; +using System.Reflection; +using Autofac; +using TechTalk.SpecFlow.Bindings; + +namespace SpecFlow.Autofac +{ + public class ContainerBuilderFinder : IContainerBuilderFinder + { + private readonly IBindingRegistry bindingRegistry; + private readonly Lazy> createScenarioContainerBuilder; + + public ContainerBuilderFinder(IBindingRegistry bindingRegistry) + { + this.bindingRegistry = bindingRegistry; + createScenarioContainerBuilder = new Lazy>(FindCreateScenarioContainerBuilder, true); + } + + public Func GetCreateScenarioContainerBuilder() + { + var builder = createScenarioContainerBuilder.Value; + if (builder == null) + throw new Exception("Unable to find scenario dependencies! Mark a static method that returns a ContainerBuilder with [ScenarioDependencies]!"); + return builder; + } + + protected virtual Func FindCreateScenarioContainerBuilder() + { + 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((MemberInfo) m, typeof(ScenarioDependenciesAttribute)))) + { + return () => (ContainerBuilder)methodInfo.Invoke(null, null); + } + } + } + return null; + + //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(); + } + } +} \ No newline at end of file diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/IContainerBuilderFinder.cs b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/IContainerBuilderFinder.cs new file mode 100644 index 000000000..412daf88e --- /dev/null +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/IContainerBuilderFinder.cs @@ -0,0 +1,10 @@ +using System; +using Autofac; + +namespace SpecFlow.Autofac +{ + public interface IContainerBuilderFinder + { + Func GetCreateScenarioContainerBuilder(); + } +} \ No newline at end of file diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/ScenarioDependenciesAttribute.cs b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/ScenarioDependenciesAttribute.cs new file mode 100644 index 000000000..d2c564c78 --- /dev/null +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/ScenarioDependenciesAttribute.cs @@ -0,0 +1,10 @@ +using System; +using System.Linq; + +namespace SpecFlow.Autofac +{ + [AttributeUsage(AttributeTargets.Method)] + public class ScenarioDependenciesAttribute : Attribute + { + } +} diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/SpecFlow.Autofac.SpecFlowPlugin.csproj b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/SpecFlow.Autofac.SpecFlowPlugin.csproj new file mode 100644 index 000000000..8de5f81d0 --- /dev/null +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/SpecFlow.Autofac.SpecFlowPlugin.csproj @@ -0,0 +1,28 @@ + + + $(SpecFlow_Runtime_TFM) + $(SpecFlow_KeyFile) + $(SpecFlow_EnableStrongNameSigning) + $(SpecFlow_PublicSign) + + true + $(MSBuildThisFileDirectory)SpecFlow.Autofac.nuspec + + true + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + + + + + + + + + + + + diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/SpecFlow.Autofac.nuspec b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/SpecFlow.Autofac.nuspec new file mode 100644 index 000000000..ba27d941f --- /dev/null +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/SpecFlow.Autofac.nuspec @@ -0,0 +1,32 @@ + + + + SpecFlow.Autofac + $version$ + SpecFlow Autofac integration plugin + Gaspar Nagy, Spec Solutions, SpecFlow + Gaspar Nagy, Spec Solutions, SpecFlow + SpecFlow plugin that enables to use Autofac for resolving test dependencies. + SpecFlow plugin that enables to use Autofac for resolving test dependencies. + en-US + http://www.specflow.org + http://go.specflow.org/specflow-nuget-icon + Copyright © 2016-2019 Gaspar Nagy, Spec Solutions, SpecFlow + false + LICENSE.txt + specflow autofac di dependency injection + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/build/SpecFlow.Autofac.props b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/build/SpecFlow.Autofac.props new file mode 100644 index 000000000..f7c6b69c3 --- /dev/null +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/build/SpecFlow.Autofac.props @@ -0,0 +1,11 @@ + + + + + %(Filename)%(Extension) + PreserveNewest + False + + + + \ No newline at end of file diff --git a/Plugins/SpecFlow.Autofac.SpecFlowPlugin/build/SpecFlow.Autofac.targets b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/build/SpecFlow.Autofac.targets new file mode 100644 index 000000000..fa45fe259 --- /dev/null +++ b/Plugins/SpecFlow.Autofac.SpecFlowPlugin/build/SpecFlow.Autofac.targets @@ -0,0 +1,8 @@ + + + + <_SpecFlow_AutofacPluginFramework Condition=" '$(TargetFrameworkIdentifier)' == '.NETCoreApp' ">netstandard2.0 + <_SpecFlow_AutofacPluginFramework Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' ">net45 + <_SpecFlow_AutofacPluginPath>$(MSBuildThisFileDirectory)\..\lib\$(_SpecFlow_AutofacPluginFramework)\SpecFlow.Autofac.SpecFlowPlugin.dll + + diff --git a/TechTalk.SpecFlow.sln b/TechTalk.SpecFlow.sln index 9d747c523..d16db9e8b 100644 --- a/TechTalk.SpecFlow.sln +++ b/TechTalk.SpecFlow.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28714.193 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.572 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Setup", "Setup", "{DCE0C3C4-5BC6-4A30-86BE-3FEFF4677A01}" EndProject @@ -88,6 +88,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AzurePipelines", "AzurePipe build.yml = build.yml EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpecFlow.Autofac.SpecFlowPlugin", "Plugins\SpecFlow.Autofac.SpecFlowPlugin\SpecFlow.Autofac.SpecFlowPlugin.csproj", "{EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -350,6 +352,18 @@ Global {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.Release|Any CPU.Build.0 = Release|Any CPU {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.VS2010IntegrationTest|Any CPU.ActiveCfg = Debug|Any CPU {02A08F81-B90F-4EB3-9C30-CE7447DE2012}.VS2010IntegrationTest|Any CPU.Build.0 = Debug|Any CPU + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug-MSTest|Any CPU.ActiveCfg = Debug|Any CPU + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug-MSTest|Any CPU.Build.0 = Debug|Any CPU + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug-NUnit|Any CPU.ActiveCfg = Debug|Any CPU + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug-NUnit|Any CPU.Build.0 = Debug|Any CPU + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug-XUnit|Any CPU.ActiveCfg = Debug|Any CPU + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Debug-XUnit|Any CPU.Build.0 = Debug|Any CPU + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.Release|Any CPU.Build.0 = Release|Any CPU + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.VS2010IntegrationTest|Any CPU.ActiveCfg = Release|Any CPU + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86}.VS2010IntegrationTest|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -374,6 +388,7 @@ Global {FA9DFEFA-4F35-4A57-91AE-64C7B5D41EAA} = {DCE0C3C4-5BC6-4A30-86BE-3FEFF4677A01} {02A08F81-B90F-4EB3-9C30-CE7447DE2012} = {A10B5CD6-38EC-4D7E-9D1C-2EBA8017E437} {48E162BC-9431-450D-8353-66D396DCB5FE} = {577A0375-1436-446C-802B-3C75C8CEF94F} + {EA16606D-6F1C-4A4D-B7A1-8A08B448CD86} = {8BE0FE31-6A52-452E-BE71-B8C64A3ED402} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A4D0636F-0160-4FA5-81A3-9784C7E3B3A4}