Ninject ToFactory works in Resharper unit tests, but not NCrunch
-
09-06-2021 - |
Question
I'm using Ninject.Extensions.Factory with Ninject 3 to create a factory, which creates different types of IFoo
based on the string provided to the factory. I've got a passing unit test, but oddly, only in the Resharper test runner. In the NCrunch test runner it fails. Is this a NCrunch config issue, or do I need to change the code?
The interface:
public interface IFooFactory
{
IFoo CreateFoo(string name);
}
The Ninject bindings:
kernel.Bind<IFooFactory>().ToFactory(() => new UseFirstParameterAsNameInstanceProvider());
kernel.Bind<IFoo>().To<BarFoo>().Named("Bar");
The test:
[Test]
public void CanCreateFooTest()
{
var factory = (IFooFactory) Kernel.GetService(typeof(IFooFactory));
var bar = factory.CreateFoo("Bar");
Assert.AreEqual(typeof(BarFoo), bar.GetType());
}
And the NCrunch exception:
System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
----> Ninject.ActivationException : Error activating IInterceptor using conditional implicit self-binding of IInterceptor
Provider returned null.
Activation path:
2) Injection of dependency IInterceptor into parameter of constructor of type IFooFactoryProxy
1) Request for IFooFactory
Suggestions:
1) Ensure that the provider handles creation requests properly.
at System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
at System.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Ninject.Infrastructure.Language.ExtensionsForIEnumerable.ToArraySlow(IEnumerable series, Type elementType) in c:\Projects\Ninject\ninject\src\Ninject\Infrastructure\Language\ExtensionsForIEnumerable.cs:line 29
at Ninject.Planning.Targets.Target`1.ResolveWithin(IContext parent) in c:\Projects\Ninject\ninject\src\Ninject\Planning\Targets\Target.cs:line 149
at Ninject.Activation.Providers.StandardProvider.GetValue(IContext context, ITarget target) in c:\Projects\Ninject\ninject\src\Ninject\Activation\Providers\StandardProvider.cs:line 114
at Ninject.Activation.Providers.StandardProvider.<>c__DisplayClass4.<Create>b__2(ITarget target) in c:\Projects\Ninject\ninject\src\Ninject\Activation\Providers\StandardProvider.cs:line 96
at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at Ninject.Activation.Providers.StandardProvider.Create(IContext context) in c:\Projects\Ninject\ninject\src\Ninject\Activation\Providers\StandardProvider.cs:line 95
at Ninject.Activation.Context.Resolve() in c:\Projects\Ninject\ninject\src\Ninject\Activation\Context.cs:line 157
at Ninject.KernelBase.<>c__DisplayClass10.<Resolve>b__c(IBinding binding) in c:\Projects\Ninject\ninject\src\Ninject\KernelBase.cs:line 386
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
at Ninject.KernelBase.System.IServiceProvider.GetService(Type service) in c:\Projects\Ninject\ninject\src\Ninject\KernelBase.cs:line 553
at FooProject.Tests.CanCreateFooTest() in C:\Projects\FooProject ...
--ActivationException
at Ninject.Activation.Context.Resolve() in c:\Projects\Ninject\ninject\src\Ninject\Activation\Context.cs:line 165
at Ninject.KernelBase.<>c__DisplayClass10.<Resolve>b__c(IBinding binding) in c:\Projects\Ninject\ninject\src\Ninject\KernelBase.cs:line 386
at System.Linq.Enumerable.WhereSelectListIterator`2.MoveNext()
at System.Linq.Enumerable.<CastIterator>d__b1`1.MoveNext()
at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
Solution
Here the code that works with NCrunch:
var kernel = new StandardKernel();
kernel.Bind<IFooFactory>().ToFactory(() => new UseFirstParameterAsNameInstanceProvider());
kernel.Bind<IFoo>().To<BarFoo>().Named("Bar");
kernel.Load<FuncModule>();
var factory = kernel.Get<IFooFactory>();
var bar = factory.CreateFoo("Bar");
Assert.Equal(typeof(BarFoo), bar.GetType());
UPDATE
This works great, and sorts out NCrunch. However, Resharper complains that it has been loaded twice. The workaround:
#if NCRUNCH
Kernel.Load<FuncModule>();
#endif
OTHER TIPS
Go into the NCrunch configuration for the unit test library and set Copy referenced assemblies to workspace to True.
The FuncModule is not loaded when running with that TestRunner. This happens in case the extension is not copied to the startup directory of the executed process.
I don't NCrunch. So I can't tell you what it is doing. But most likely it copies the assemblies in a different way than the R# test runner. You could load the extensions manually but this feels like a hack.
I have been using harriyott's suggestion for a year or so. But then this problem happend on our TFS-Buildserver, too. So now I avoid Ninject automatically loading of all Extensions and Load them manually. This avoids the #if
, #endif
and the same code will run on Resharper and NCrunch:
var kernel = new StandardKernel(new NinjectSettings { LoadExtensions = false});
kernel.Load<FuncModule>();
the rest is unchanged:
kernel.Bind<IFooFactory>().ToFactory(() => new UseFirstParameterAsNameInstanceProvider());
kernel.Bind<IFoo>().To<BarFoo>().Named("Bar");
var factory = kernel.Get<IFooFactory>();
var bar = factory.CreateFoo("Bar");
Assert.Equal(typeof(BarFoo), bar.GetType());