Why can't I inject value null with Ninjects ConstructorArgument?
Asked Answered
B

4

5

When using Ninjects ConstructorArgument you can specify the exact value to inject to specific parameters. Why can't this value be null, or how can I make it work? Maybe it's not something you'd like to do, but I want to use it in my unit tests.. Example:

public class Ninja
{
    private readonly IWeapon _weapon;
    public Ninja(IWeapon weapon)
    {
        _weapon = weapon;
    }
}

public void SomeFunction()
{
    var kernel = new StandardKernel();
    var ninja = kernel.Get<Ninja>(new ConstructorArgument("weapon", null));
}
Bulgarian answered 19/4, 2010 at 12:14 Comment(1)
I'm not familiar with ninject, but I think the issue is that IoC-container uses type information to find a suitable constructor and it can't find it out by null value. You'd better to search for answer in documentation or in its code. In one library that I used instance of corresponding Type object has to be passed instead of null. There might be some similar solution here as well.Inextirpable
A
7

Looking at the source (and the stack trace I got by reproing which you omitted :P)

This is because it's binding to a different overload of the ConstructorArgument ctor than the normal usage (i.e., where you're passing a Value Type or a non-null Reference Type) does.

The workaround is to cast the null to Object:-

var ninja = kernel.Get<Ninja>( new ConstructorArgument( "weapon", (object)null ) );

Ninject 2 source:

public class ConstructorArgument : Parameter
{
    /// <summary>
    /// Initializes a new instance of the <see cref="ConstructorArgument"/> class.
    /// </summary>
    /// <param name="name">The name of the argument to override.</param>
    /// <param name="value">The value to inject into the property.</param>
    public ConstructorArgument(string name, object value) : base(name, value, false) { }

    /// <summary>
    /// Initializes a new instance of the <see cref="ConstructorArgument"/> class.
    /// </summary>
    /// <param name="name">The name of the argument to override.</param>
    /// <param name="valueCallback">The callback to invoke to get the value that should be injected.</param>
    public ConstructorArgument(string name, Func<IContext, object> valueCallback) : base(name, valueCallback, false) { }
}

Repro:

public class ReproAndResolution
{
    public interface IWeapon
    {
    }

    public class Ninja
    {
        private readonly IWeapon _weapon;
        public Ninja( IWeapon weapon )
        {
            _weapon = weapon;
        }
    }

    [Fact]
    public void TestMethod()
    {
        var kernel = new StandardKernel();
        var ninja = kernel.Get<Ninja>( new ConstructorArgument( "weapon", (object)null ) );
    }
}

Lesson? You'd be crazy not to download the latest source and look at it. Great comments, nice clean codebase. Thanks again to @Ian Davis for that tip/prodding!

Adventuresome answered 19/4, 2010 at 12:59 Comment(2)
Thx for the explanation. Don't think I want to go around casting nulls to objects though, but this seems to answer my question! Perhaps I gotta check out the latest source :-)Bulgarian
@stiank81: As my comment on @Finglas' answer (which I believe is the right answer even if I agree this should be The Accepted One :P) says, the reason it's ugly is because its not intended to be done in normal usage (Didnt we cover ConstructorArgument being a bad default approach in another question ? :P) @Finglas: Thanks, and thanks for removing the other stuffAdventuresome
R
3

I don't know Ninject, but AFAIK constructor injection is commonly used for mandatory dependencies and thus null makes little sense in this context. If the dependency is not mandatory the type should provide a default constructor and use property injection instead.

This post provides additional info.

Refrain answered 19/4, 2010 at 12:41 Comment(3)
I +1d this because it seemed more logical than Gerrie's answer. On reflection I dont really agree though as it can be used for Value Types as well as Reference Types and the point doesnt really generalise well in that context. What one wants to guard against is unspecified, randomly uninitialised and/or dependencies that are not specified in a clear manner that tools can help you identify dependency chains with.Adventuresome
Property injection is an acceptable solution for this? If so this sounds like a reasonable thing to do. Thx for the link.Bulgarian
@stiank81: While property injection might accomplish the basic thing you're trying to achieve (lots of null values), in doing so you'll be losing you a lot in Code Cleanliness, so I'd say no - I refer you to @Finglas' answer.Adventuresome
C
3

I want to use it in my unit tests

There is no need to use an IOC container for unit tests. You should use the container to wire you application together at runtime, and nothing more. And if that begins to hurt, its a smell indicating your class is getting out of hand (SRP violation?)

Your unit test would then be in this example:

var ninja = new Ninja(null);

The above is legit C# code, and passing a null reference for unit testing is a perfectly valid way of unit testing areas which don't need the dependency.

Caulk answered 19/4, 2010 at 13:3 Comment(2)
+1 Important point even if it doesnt answer the question (although the fact that this is the case is probably why this issue (resolving to wrong overload in case of null) hasn't been considered an issue.Adventuresome
@Ruben: That's fine, besides that's what SO is all about. Improving and learning.Caulk
B
0

This is probably unsupported because constructor arguments can be value types too.

Berserk answered 19/4, 2010 at 12:27 Comment(1)
This prompted me to find the real answer as it felt wrong. But it also prompted me on reflection to disagree with Brian's answer (I'd leave both +0 as but too late to undo the other and while both provided conceptual insight, I dont believe they represent anything that Ninject is trying to encourage/prevent/force)Adventuresome

© 2022 - 2024 — McMap. All rights reserved.