Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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));
}
like image 286
stiank81 Avatar asked Apr 19 '10 12:04

stiank81


3 Answers

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!

like image 168
Ruben Bartelink Avatar answered Sep 29 '22 22:09

Ruben Bartelink


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.

like image 22
Brian Rasmussen Avatar answered Sep 29 '22 23:09

Brian Rasmussen


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.

like image 45
Finglas Avatar answered Sep 29 '22 21:09

Finglas