Skip to content

Verification Pattern #1

@ENikS

Description

@ENikS

Verification Pattern

Tested Member Types

  • Field
  • Property
  • Parameter

Types of Injection

Unity container supports three different types of dependency injection:

  • Implicit dependencies
  • Annotated dependencies
  • Injected dependencies

Implicit Dependencies

Dependency is implicit if no injection directives such as InjectionMember or DependencyAttribute are present but the member is still required to be injected. An example of implicit dependency would be a constructor's and a method's parameters:

public class PocoType 
{
    public int Field;

    public int Property { get; set; }

    [InjectionConstructor]
    public NoDefault_Value(int value) { }
    
    [InjectionMethod]
    public void Method(int value) { }
}

Although parameter value is not explicitly annotated, the method and the constructor are both annotated with injection attribute, so the corresponding parameters are implicitly marked for injection.

With Defaults

All members, Fields, Properties, and Parameters could have default value assigned to it at design time:

public class PocoTypeWithDefaults 
{
    public int Field = 111;

    public int Property { get; set; } = 222;

    [InjectionConstructor]
    public NoDefault_Value(int value = 333) { }
    
    [InjectionMethod]
    public void Method(int value = 444) { }
}

When resolving such types, in case when value could not be resolved, it should fall back to provided default value. The parameter value should be assigned the default (333 or 444 respectively).

If resolution is successful members should be assigned the resolved value.

Test Pattern

Unity tests both cases, where dependencies are registered and when container is empty. This leads to the following test cases:

  • Unregistered_Implicit
  • Unregistered_Implicit_WithDefault
  • Registered_Implicit
  • Registered_Implicit_WithDefault

Annotated Dependencies

Dependency injection could be specified by annotating members with attributes. There are two types of dependencies:

  • Required dependencies
  • Optional dependencies

Required Dependencies

Required dependencies are annotated with DependencyAttribute

public class TypeWithRequiredDependency
{
    [Dependency]
    public int Field;

    [Dependency]
    public int Property { get; set; }

    [InjectionConstructor]
    public TypeWithRequiredDependency([Dependency] int value) { }

    [InjectionMethod]
    public void Method([Dependency] int value) { }
}

Resolution of required dependencies is different for Parameters compared with Fields and Properties.

Required Parameters

Based on availability of default value, when resolving it, Unity will throw if no default value is available or fall back on default value if it exists. So the following will throw if can not be resolved:

public class TypeWithRequiredDependency
{
    [InjectionConstructor]
    public TypeWithRequiredDependency([Dependency] int value) { }

    [InjectionMethod]
    public void Method([Dependency] int value) { }
}

this will never throw:

public class TypeWithDefaults
{
    [InjectionConstructor]
    public TypeWithDefaults([Dependency] int value = 22) { }

    [InjectionMethod]
    public void Method([Dependency] int value = 33) { }
}
Required Properties and Fields

Properties and Fields annotated with DependencyAttribute will always throw if value could not be resolved. The following example will throw if int is not registered:

public class TypeWithRequiredDependency
{
    [Dependency]
    public int Field  = 33;

    [Dependency]
    public int Property { get; set; }
}
Test Pattern

Unity tests both cases, where dependencies are registered and when container is empty. This leads to the following test cases:

  • Unregistered_Required
  • Unregistered_Required_WithDefault
  • Registered_Required
  • Registered_Required_WithDefault

Optional Dependencies

Optional dependencies are annotated with OptionalDependencyAttribute.

public class TypeWithOptionalDependency
{
    [OptionalDependency]
    public int Field = 11;

    [OptionalDependency]
    public int Property { get; set; }

    [InjectionConstructor]
    public TypeWithOptionalDependency([OptionalDependency] int value) { }

    [InjectionMethod]
    public void Method([OptionalDependency] int value = 33) { }
}

Unity attempts to inject Optional parameters with resolved values but instead of throwing if it can't satisfy dependency, it just falls back on default. Resolution of Optional dependencies is slightly different for Parameters and Fields or Properties.

Optional Parameters

Optional parameters are initialized as follows:

  • Resolved value, if available
  • DefaultValue is available
  • default for the Type

Resolving Optional dependencies never throws.

Optional Properties and Fields

Properties and Fields annotated for Optional injection are inizialized as follows:

  • Resolved value, if available
  • If can't resolve, leave the member as is

When optional dependency can not be satisfied by the container, the value already assigned to it is stay the same. This is done so to allow design time initialization:

public class TypeWithOptionalDependency
{
    [OptionalDependency]
    public int Field = 11;

    [OptionalDependency]
    public int Property { get; set; }
}

The logic behind this design decision is as follows:

  • For the Property, in case dependency resolution failed, initializing it with null is redundant (it is already null). But if property is initialized with initial value, it will override that value and data will be lost.

  • Field is initialized with default value. Assigning default (value of 0) in case of resolution failure is incorrect. It should be left as 11.

Test Pattern

Unity tests both cases, where dependencies are registered and when container is empty. This leads to the following test cases:

  • Unregistered_Optional
  • Unregistered_Optional_WithDefault
  • Registered_Optional
  • Registered_Optional_WithDefault

Injected Dependencies

Unity uses InjectionMember derived types to specify injection targets on classes. For example ths following type

public class PocoType
{
    public int Field;

    public int Property { get; set; }

    public PocoType(int value) { }
    
    public void Method(int value) { }

    public void Method([Dependency(name)]string value) { }

    public void Method([OptionalDependency]long value) { }

    public void Method(IUnityContainer value) { }
}

is not annotated for injection.

To specify what to inject a type must be registered with the container and provide an InjectionMember for each field, property, method, or constructor that must be injected.

Unity recognizes three types of injection:

  • Implicit injection
  • Explicit injection

Implicit Injection

Implicit injection is only applicable to methods and constructors.

It is possible to instruct the container to invoke specific constructor or a method by specifying the signature without providing any injection info for corresponding parameters. For example, a registration like this would instruct the container to invoke a method Method(int value):

Container.RegisterType(type, new InjectionConstructor(typeof(int)))
         .RegisterType(type, new InjectionMethod("Method", typeof(int)));

With no injection configured for each parameter, the container will fall back to rules for implicit dependencies:

  • if parameter not annotated, it considered required dependency
  • if parameter is annotated, it will be resolved according to annotation

Explicit injection

Injection is explicit when exact directions are given on how to resolve dependency. For example, to be explicit, the same registration could be rewritten like this:

Container.RegisterType(type, new InjectionConstructor(new InjectionParameter(11))))
         .RegisterType(type, new InjectionMethod("Method", new InjectionParameter(22)));
Test Pattern

Unity tests both cases, where dependencies are registered and when container is empty. This leads to the following test cases:

Dependency Resolution

  • Implicit
    • No Default
      • Value Type ✔️
      • Class ✔️
    • Default Value
      • Value Type ✔️
      • Class ✔️
  • Annotated
    • Required ( Anonymous / Named )
      • No Default
        • ValueType
        • Class
        • Generic
      • Default Value
        • ValueType
        • Class
        • Generic
    • Optional ( Anonymous / Named )
      • No Default
        • ValueType
        • Class
        • Generic
      • Default Value
        • ValueType
        • Class
        • Generic
  • Injected
    • Required ( Anonymous / Named )
      • No Default
        • ValueType
        • Class
        • Generic
      • Default Value
        • ValueType
        • Class
        • Generic
    • Optional ( Anonymous / Named )
      • No Default
        • ValueType
        • Class
        • Generic
      • Default Value
        • ValueType
        • Class
        • Generic

Tested Variants

  • Dependency Type (None, Annotated, Injected)
    • No Default
      • Registered
        • ValueType
        • Class
        • Generic
      • Missing
        • ValueType
        • Class
        • Generic
    • Default Value
      • Registered
        • ValueType
        • Class
        • Generic
      • Missing
        • ValueType
        • Class
        • Generic

Parameter Injection Pattern

  • Injected implicitly
  • Injected with member Type identifier

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions