Tutorial - Using Dependency Injection (Unity) in a test application
with CodeTrigger
To work with these sample projects you will need CodeTrigger installed, Visual Studio
2013-2017, and a test database with some data.
This tutorial/sample is a follow-on from a previous tutorial/sample
that you can find here: Tutorial - Generating a sample repository-pattern test application with CodeTrigger
You need to perform the steps in the previous article before carrying on with the steps in this article.
This guide has been updated for version 5.1.0.0 (released June 10th 2017)
|
|
Step 8 - Regenerate the code with Injection attributes enabled
Build the solution you created in the previous articule (steps 1 - 7) and confirm it builds successfully.
Return to the tab on the CodeTrigger user interface called 'Advanced Options'. In addition to the settings you chose previously, now tick 'Enable for dependency injection pattern (Unity & Ninject)
ie, The settings in the Advanced options section should be :
Generate interface definitions: Ticked
Generate using repository pattern (enable Mocking eg Moq):
Ticked
Enable for dependency injection pattern (Unity & Ninject): Ticked
Generate data versioning and concurrency management: Ticked
Leave all other settings the same as in the previous tutorial and click the 'Generate Code' button. In addition to the code previously generated,
CodeTrigger will decorate specific properties with a newly defined attribute, marking them as Injection points.
|
|
Step 9 - Configure a Unity container & RUN!
Build the solution you created in the previous step (step 8) and confirm it builds successfully.
Now we create a basic test to demonstrate the usage of using CodeTrigger generated code with the Unity container & Dependency Injection.
The following steps are all applied in the SampleRepositoryPattern.Tests project you created in the previous tutorial
(i) Add Microsoft Practices Unity to your test project, either by referencing the Unity assembly or using Nuget to add the Unity package and dependencies
(ii) Create a folder called Extensions in the project.
(iii) Add the following class files to the Extensions folder
InjectionPointPolicyUnity.cs
The contents of this file are listed at the end of the article.
Thats it, you are ready to consume the business and data layer via your repository pattern, and inject your dependencies
In the UnitTest1 class, add the following using namespace declarations:
using Microsoft.Practices.Unity;
using SampleRepositoryPattern.Tests.Extensions;
Now all you have to do is resolve your business layer objects using the Unity container rather than directly.
You can setup the container to do this by adding our custom injection point extension and registering the required types, like so:
/*Create the Unity container*/
var container = new UnityContainer();
container.AddNewExtension<CustomUnityPropertyInjectAttributeExtension>();
/*Create repository factory and declare the repositories you need for your build configuration*/
RF repoFactory = RF.New();
ICategoriesRepository categoriesRepository = repoFactory.CategoriesRepository;
IProductsRepository productsRepository = repoFactory.ProductsRepository;
/*Let Unity know about the repositories you are using*/
container.RegisterInstance(categoriesRepository);
container.RegisterInstance(productsRepository);
container.RegisterType<BOCategories, BOCategories>();
And then to get the Unity container to construct the business object instance with all the required components injected (in this example just the repository):
BOCategories boCategory = container.Resolve<BOCategories>();
As you can see, we dont explicitly create the CategoriesRepository or assign it to the boCategories object, the repository is created by the unity container
and injected into the boCategories object, because CodeTrigger has automatically decorated the BOCategories class with the attribute [InjectionPoint]
at specific points. This attribute has been designed to be agnostic so it is recognised in this example by the Unity container, and in other samples by the Ninject container,
so you can swap the container implementation without changing the Business project tier or the definitions of the business objects.
The code listing below shows a complete test definition called create_update_delete_Category_usingUnity() which we can use add to our UnitTest1 class, and which creates, updates, and
deletes a test category object using a repository implementation that has been injected using the Unity container.
|
And thats it, you have successfully created and unit tested using the Unity container, a multi-tier, database enabled, Repository pattern & Dependency Injection based application, using CodeTrigger.
We have used the Unity container to instantiate our objects and Inject an implementation of a repository into the object, and we have done it without tie-ing our business objects definition and
implementation to any particular container implementation, so that we know we can swap out our Unity container and use Ninject instead without regeneration or re-compiling the business tier of our project.
|
//new addition to our UnitTest1 class
[TestMethod]
public void create_update_delete_Category_usingUnity()
{
string initialName = "My Test Category";
string initialDescription = "initial desc";
string updatedDescription = "updated desc";
//setup the test
var container = new UnityContainer();
container.AddNewExtension<CustomUnityPropertyInjectAttributeExtension>();
RF repoFactory = RF.New();
/*declare as many of these repositories as you need for your build configuration*/
ICategoriesRepository categoriesRepository = repoFactory.CategoriesRepository;
IProductsRepository productsRepository = repoFactory.ProductsRepository;
container.RegisterInstance(categoriesRepository);
container.RegisterInstance(productsRepository);
container.RegisterType<BOCategories, BOCategories>();
BOCategories boCategory = container.Resolve<BOCategories>();
try
{
//test saving a new category
boCategory.CategoryName = initialName;
boCategory.Description = initialDescription;
boCategory.SaveNew();
//test#1 that the versioning is working properly, here the version should be 0 when we load
int categoryID = (int)boCategory.CategoryID;
boCategory.Init(categoryID);
Assert.AreEqual(boCategory.CtrVersion, 0);
//test updating an existing category
boCategory.Description = updatedDescription;
boCategory.Update();
boCategory.Init(categoryID);
Assert.AreEqual(boCategory.Description, updatedDescription);
//test#2 that the concurrency & versioning is working properly, here the
//incremented version should be 1 after previous update
Assert.AreEqual(boCategory.CtrVersion, 1);
//test that we can delete
boCategory.Delete();
try
{
boCategory = container.Resolve<BOCategories>();
boCategory.Init(categoryID);
Assert.Fail("Test failed, object has been deleted and should no longer exist");
}
catch (Exception ex)
{
//test passed because we are expecting this exception as we are trying to initialise
//with an id belonging to an object that no longer exists (since we deleted it above)
StringAssert.Contains(ex.Message, "Object reference");
}
}
catch
{
//catch all to try and get rid of this test category so we can run this test again
boCategory.Delete();
}
}
//InjectionPointPolicyUnity.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Unity;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity.ObjectBuilder;
using SampleRepositoryPattern.Business.Repository.Interfaces;
namespace SampleRepositoryPattern.Tests.Extensions
{
public class InjectionPointPolicyUnity : PropertySelectorBase<InjectionPointAttribute>
{
protected override IDependencyResolverPolicy CreateResolver(System.Reflection.PropertyInfo property)
{
var attributes =
property.GetCustomAttributes(typeof(InjectionPointAttribute), false)
.OfType<InjectionPointAttribute>()
.ToList();
if (attributes.Count > 0)
return new NamedTypeDependencyResolverPolicy(property.PropertyType, null);
return null;
}
}
public class CustomUnityPropertyInjectAttributeExtension : UnityContainerExtension
{
protected override void Initialize()
{
Context.Policies.SetDefault(typeof(Microsoft.Practices.ObjectBuilder2.IPropertySelectorPolicy),
new InjectionPointPolicyUnity());
}
}
}
|