hgs yükleme
My Cart (0)  |  My Orders  |  My Downloads  |  My Auction  |  My Account  |  Help

Login |Register        Search

Patterns and Practices in DNN - 1: Testable Controllers

                Print      Add To Favorite     Add To Watch List     Contact Author

Creator: host   5/26/2012 11:10:25 PM    Author: Charles Nurse   Source: http://www.dotnetnuke.com/Resources/Blogs/EntryId/3310/DotNetNuke-Patterns-and-Practices-2-Testable-Controllers.aspx   Views: 2730    0    0  
Module develop Patterns and Practices Dnn development

Posted by: Charles Nurse

So here it is – the first real post in our series on DotNetNuke Patterns and Practices.  How do we make our “Controllers” testable.

Historically, DotNetNuke has used a “Repository” style for manipulating entities in the business layer (rather than an Active Record style) – a lightweight Entity class – usually suffixed with Info, and a Repository class which has traditionally used Controller as a suffix. 

This naming strategy was present initially  in the iBuySpy Portal Starter-Kit upon which DotNetNuke is based.  Thus, for instance, if the business layer needs to model Task objects, there will be a TaskInfo entity and a TaskController repository class.

Back in 2002/3 when DotNetNuke was first created, developers were not so concerned about whether they could write Unit Tests, so the pattern of use was that whenever a “controller” was needed it was constructed on the fly.  If that controller in turn needed to call a 2nd controller then it was also constructed on the fly.

The problem, from a modern perspective is that it is difficult to write Unit Tests using this pattern, as there are unknown dependencies that cannot be mocked or faked.

Dependency Injection

One of the ways to solve this is to use Dependency Inversion.  This principle is one (the D) of the SOLID Principles of Object Oriented Design espoused by “Uncle” Bob Martin and others (we will review these principles in future articles in this series).  The “Dependency Inversion Principle” says that any dependencies should be based on abstractions not concretions.

Dependency Injection  supports this by injecting any dependency in the form of an Interface (or abstract base class), usually through the Constructor.

So if ControllerA has a dependency on IControllerB then ControllerA’s constructor should look like the following:

1 private IControllerB _controllerB;
3 public ControllerA(IControllerB controllerB)
4 {
5     _controllerB = controllerB;
6 }

The class that is constructing a ControllerA would need to also construct a concrete implementation (ControllerB) of IControllerB and pass it as a parameter.

1 IControllerB controllerB = new ControllerB();
3 ControllerA controllerA = new ControllerA(controllerB);

In a Unit Test, the dependent interface can then be mocked, using a mocking framework like Moq or RhinoMocks or faked by creating a FakeControllerB which has a known behavior.

Dependency Injection Containers

We can make this simpler by using a Dependency Injection Container.  DotNetNuke has a simple Container and we can use this container to register all of our controllers. This can reduce some of the plumbing that we have to do.

For example, we can create an instance of ControllerB and add it to the Container.

1 ComponentFactory.RegisterComponentInstance<IControllerB>(new ControllerB);

Now we can modify the ControllerA class by adding a second constructor with no parameters.

1 public ControllerA()
2     : this(ComponentFactory.GetComponent<IControllerB>())
3 {
4 }

With these changes in place we can go back to our original pattern of constructing an instance of ControllerA.

1 var controllerA = new ControllerA();

When writing Unit Tests the dependency on IControllerB can be resolved by mocking or faking IControllerB and adding the mock (or fake) to the Container as part of the test setup.

There are some examples in the core of this pattern.

Service Location

An alternative approach to Dependency Injection is to use Service Location.    In Service Location we call some service to locate an instance of the Interface that we need.

The Container in DotNetNuke is actually an example of Service Location. 

1 var b = ComponentFactory.GetComponent<IControllerB>();

In 6.2 we have introduced an abstract base ServiceLocator class that manages the service location.

01 public abstract class ServiceLocator<TContract, TSelf> where TSelf : ServiceLocator<TContract, TSelf>, new()
02 {
03     private static TContract _instance;
04     private static bool _isInitialized;
06     protected static Func<TContract> Factory { get; set; }
08     public static TContract Instance
09     {
10         get
11         {
12             if (!_isInitialized)
13             {
14                 if (Factory == null)
15                 {
16                     var controllerInstance = new TSelf();
17                     Factory = controllerInstance.GetFactory();
18                 }
20                 _instance = Factory();
21                 _isInitialized = true;
22             }
24             return _instance;
25         }
26     }
28     public static void SetTestableInstance(TContract instance)
29     {
30         _instance = instance;
31         _isInitialized = true;
32     }
34     protected abstract Func<TContract> GetFactory();
35 }

How this works is best shown with an example.  In the CTP we have refactored the RoleController to use this new service location pattern.

We have extracted an interface – IRoleController – from the original RoleController class:

01 public interface IRoleController
02 {
03     int AddRole(RoleInfo role);
04     void DeleteRole(RoleInfo role);
05     RoleInfo GetRole(int portalId, Func<RoleInfo, bool> predicate);
06     IList<RoleInfo> GetRoles(int portalId);
07     IList<RoleInfo> GetRoles(int portalId, Func<RoleInfo, bool> predicate);
08     IDictionary<string, string> GetRoleSettings(int roleId);
09     void UpdateRole(RoleInfo role);
10     void UpdateRoleSettings(RoleInfo role, bool clearCache);
11 }

Next we implemented a concrete implementation of the Interface – RoleControllerImpl (which is too long to show in its entirety here). 

Finally we created a class which inherits from the new ServiceLocator class – TestableRoleController.

1 public class TestableRoleController : ServiceLocator<IRoleController, TestableRoleController>
2 {
3     protected override Func<IRoleController> GetFactory()
4     {
5         return () => new RoleControllerImpl();
6     }
7 }

This now works in a similar way to the way we use our providers. To call a method on the RoleController, we call the Instance method, which returns the current Instance of the interface.

1 TestableRoleController.Instance.AddRole(role);

The Instance property of the abstract ServiceLocator base class will return an instance of the interface (IRoleController).  By default the GetFactory method is setting the internal instance to be the concrete implementation (RoleControllerImpl) but the base class also has a method SetTestableInstance.  We can now use this method to change the internal instance to a Mock or Fake, allowing us to test methods which call the TestableRoleController.

We believe that this is a simple pattern which will encourage creation of Testable Controllers, and as we move closer to the 6.2 Release expect to see more of the core converted to this pattern.  Of course, the existing methods will be retained for binary compatibility, but we are also taking the opportunity to clean up some of our old APIs.

Note: The usage of the TestableRoleController is temporary – ultimately RoleController will be refactored to inherit from the new ServiceLocator base class.

Rating People: 4   Average Rating:     

     DnnModule.com is built to provide DNN quality modules and DNN skins, some of them are free, some not. We wish these stuffs (free or not ) can be useful to you.

     Besides that, we also provide a full range of professional services, ranging from web site build, seo, system management, administration, support, senior consultancy and security services. We act as if your development project or network was ours, with care and respect. We are not satisfied until it works the way you want it to, and we don't silently ignore found issues as somebody else's problem.