When Fitnesse instantiates a fixture, it looks for a default public constructor.
However, I would like to constructor inject whatever application services I want to consume in the fixture.
I.e. I would like to write my fixture this this...
public class MyColumnFixture : ColumnFixture {
private readonly IApplicationService _applicationService;
public ManualExitSetupFixture(IApplicationService applicationService) {
_applicationService = applicationService;
}
public void DoStuff(string arg1) {
_applicationService.DoStuff(arg1);
}
}
The best I've managed to come up with so far is to expose the container as a singleton (see below). But there has to be a better way. Autofac integrates so nicely with so many of the other technologies we use.
public class AutofacIntegration {
private static IContainer _container;
public static IContainer Container {
get {
if (_container == null) {
var builder = new ContainerBuilder();
builder.RegisterModule<MyApplicationModule>();
_container = builder.Build();
}
return _container;
}
}
}
public class MyColumnFixture : ColumnFixture {
private readonly IApplicationService _applicationService;
public ManualExitSetupFixture() {
_applicationService = AutofacIntegration.Container.Resolve<IApplicationService>();
}
public void DoStuff(string arg1) {
_applicationService.DoStuff(arg1);
}
}
EDIT: Including more detail in my attempts to make this work based on assistance from Mike...
I have created a class which is copy-pasted from the decompiled code for CreateDefault decompiled from Fitsharp...
public class CreateDefault<T, P> : Operator<T, P>, CreateOperator<T> where P : class, Processor<T>
{
public bool CanCreate(NameMatcher memberName, Tree<T> parameters)
{
return true;
}
public TypedValue Create(NameMatcher memberName, Tree<T> parameters)
{
...
}
}
...and registered this in the SuiteConfig.xml...
<suiteConfig>
<ApplicationUnderTest>
<AddAssembly>..\..\Services\Win\MyProj.Fitnesse\MyProj.Fitnesse\bin\Debug\MyProj.Fitnesse.dll</AddAssembly>
</ApplicationUnderTest>
<Fit.Operators>
<Add>MyProj.Fitnesse.CreateDefault`2</Add>
</Fit.Operators>
</suiteConfig>
...this gives the following exception as it tries to load in my CreateDefault<,> class.
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> fitSharp.Machine.Exception.CreateException: Constructor with 0 parameter(s) failed for type 'MyProj.Fitnesse.CreateDefault`2'. ---> System.ArgumentException: Cannot create an instance of MyProj.Fitnesse.CreateDefault`2[T,P] because Type.ContainsGenericParameters is true.
at System.RuntimeType.CreateInstanceCheckThis()
at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark)
at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
at System.Reflection.Assembly.CreateInstance(String typeName, Boolean ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
at fitSharp.Machine.Engine.RuntimeType.CreateInstance()
at fitSharp.Machine.Engine.CreateDefault`2.CreateWithoutParameters(RuntimeType runtimeType)
--- End of inner exception stack trace ---
at fitSharp.Machine.Engine.CreateDefault`2.CreateWithoutParameters(RuntimeType runtimeType)
at fitSharp.Machine.Engine.CreateDefault`2.Create(NameMatcher memberName, Tree`1 parameters)
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)
at fitSharp.Machine.Engine.MethodMember.TryInvoke(Object[] parameters)
at fitSharp.Machine.Engine.ReflectionMember.Invoke(Object[] parameters)
at fitSharp.Machine.Engine.ProcessorBase`2.Operate[O](Object[] parameters)
at fitSharp.Machine.Engine.Operators`2.Add(String operatorName)
--- End of inner exception stack trace ---
at fitSharp.Machine.Engine.ProcessorExtension.InvokeWithThrow[T](Processor`1 processor, TypedValue instance, MemberName memberName, Tree`1 parameters)
at fitSharp.Machine.Application.SuiteConfiguration.LoadNode(String typeName, XmlNode methodNode)
at fitSharp.Machine.Application.SuiteConfiguration.LoadXml(String configurationXml)
at fitSharp.Machine.Application.ArgumentParser.InvokeArgumentHandler(String switch, String argumentValue)
at fitSharp.Machine.Application.ArgumentParser.Parse(IList`1 commandLineArguments)
at fitSharp.Machine.Application.Shell.Run(IList`1 commandLineArguments)
EDIT: Big thanks to Mike, this is now working a treat.
The implementation is really quite simple, and I'm sure it can be improved upon, but here's a quick how-to...
- Define a custom CreateOperator
- Create the SuiteConfig.xml to point to the dll and load the custom create operator
- Specify the SuiteConfig when launching the runner
- Register the fixtures
My CreateOperator code...
public class AutofacCreateOperator : CellOperator, CreateOperator<Cell>
{
private static IContainer _container;
public AutofacCreateOperator()
{
var builder = new ContainerBuilder();
builder.RegisterModule<FitnesseModule>();
_container = builder.Build();
}
public bool CanCreate(NameMatcher memberName, Tree<Cell> parameters)
{
return _container.ComponentRegistry.IsRegistered(new TypedService(Type.GetType(memberName.MatchName)));
}
public TypedValue Create(NameMatcher memberName, Tree<Cell> parameters)
{
return new TypedValue(_container.Resolve(Type.GetType(memberName.MatchName)));
}
}