Products

Solutions

Resources

Partners

Community

About

New Community Website

Ordinarily, you'd be at the right spot, but we've recently launched a brand new community website... For the community, by the community.

Yay... Take Me to the Community!

The Community Blog is a personal opinion of community members and by no means the official standpoint of DNN Corp or DNN Platform. This is a place to express personal thoughts about DNNPlatform, the community and its ecosystem. Do you have useful information that you would like to share with the DNN Community in a featured article or blog? If so, please contact .

The use of the Community Blog is covered by our Community Blog Guidelines - please read before commenting or posting.


DotNetNuke Patterns and Practices - 2: Testable Controllers

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:

private IControllerB _controllerB;

public ControllerA(IControllerB controllerB)
{
    _controllerB = controllerB;
}

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

IControllerB controllerB = new ControllerB();

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.

ComponentFactory.RegisterComponentInstance<IControllerB>(new ControllerB);

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

public ControllerA()
    : this(ComponentFactory.GetComponent<IControllerB>())
{
}

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

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. 

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

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

    public abstract class ServiceLocator<TContract, TSelf> where TSelf : ServiceLocator<TContract, TSelf>, new()
    {
        private static TContract _instance;
        private static bool _isInitialized;

        protected static Func<TContract> Factory { get; set; }

        public static TContract Instance
        {
            get
            {
                if (!_isInitialized)
                {
                    if (Factory == null)
                    {
                        var controllerInstance = new TSelf();
                        Factory = controllerInstance.GetFactory();
                    }

                    _instance = Factory();
                    _isInitialized = true;
                }

                return _instance;
            }
        }

        public static void SetTestableInstance(TContract instance)
        {
            _instance = instance;
            _isInitialized = true;
        }

        protected abstract Func<TContract> GetFactory();
    }

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:

    public interface IRoleController
    {
        int AddRole(RoleInfo role);
        void DeleteRole(RoleInfo role);
        RoleInfo GetRole(int portalId, Func<RoleInfo, bool> predicate);
        IList<RoleInfo> GetRoles(int portalId);
        IList<RoleInfo> GetRoles(int portalId, Func<RoleInfo, bool> predicate);
        IDictionary<string, string> GetRoleSettings(int roleId);
        void UpdateRole(RoleInfo role);
        void UpdateRoleSettings(RoleInfo role, bool clearCache);
    }

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.

    public class TestableRoleController : ServiceLocator<IRoleController, TestableRoleController>
    {
        protected override Func<IRoleController> GetFactory()
        {
            return () => new RoleControllerImpl();
        }
    }

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.

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.

Comments

Comment Form

Only registered users may post comments.

NewsArchives


Aderson Oliveira (22)
Alec Whittington (11)
Alessandra Daniels (3)
Alex Shirley (10)
Andrew Hoefling (3)
Andrew Nurse (30)
Andy Tryba (1)
Anthony Glenwright (5)
Antonio Chagoury (28)
Ash Prasad (37)
Ben Schmidt (1)
Benjamin Hermann (25)
Benoit Sarton (9)
Beth Firebaugh (12)
Bill Walker (36)
Bob Kruger (5)
Bogdan Litescu (1)
Brian Dukes (2)
Brice Snow (1)
Bruce Chapman (20)
Bryan Andrews (1)
cathal connolly (55)
Charles Nurse (163)
Chris Hammond (213)
Chris Paterra (55)
Clint Patterson (108)
Cuong Dang (21)
Daniel Bartholomew (2)
Daniel Mettler (181)
Daniel Valadas (48)
Dave Buckner (2)
David Poindexter (12)
David Rodriguez (3)
Dennis Shiao (1)
Doug Howell (11)
Erik van Ballegoij (30)
Ernst Peter Tamminga (80)
Francisco Perez Andres (17)
Geoff Barlow (12)
George Alatrash (12)
Gifford Watkins (3)
Gilles Le Pigocher (3)
Ian Robinson (7)
Israel Martinez (17)
Jan Blomquist (2)
Jan Jonas (3)
Jaspreet Bhatia (1)
Jenni Merrifield (6)
Joe Brinkman (274)
John Mitchell (1)
Jon Henning (14)
Jonathan Sheely (4)
Jordan Coopersmith (1)
Joseph Craig (2)
Kan Ma (1)
Keivan Beigi (3)
Kelly Ford (4)
Ken Grierson (10)
Kevin Schreiner (6)
Leigh Pointer (31)
Lorraine Young (60)
Malik Khan (1)
Matt Rutledge (2)
Matthias Schlomann (16)
Mauricio Márquez (5)
Michael Doxsey (7)
Michael Tobisch (3)
Michael Washington (202)
Miguel Gatmaytan (3)
Mike Horton (19)
Mitchel Sellers (40)
Nathan Rover (3)
Navin V Nagiah (14)
Néstor Sánchez (31)
Nik Kalyani (14)
Oliver Hine (1)
Patricio F. Salinas (1)
Patrick Ryan (1)
Peter Donker (54)
Philip Beadle (135)
Philipp Becker (4)
Richard Dumas (22)
Robert J Collins (5)
Roger Selwyn (8)
Ruben Lopez (1)
Ryan Martinez (1)
Sacha Trauwaen (1)
Salar Golestanian (4)
Sanjay Mehrotra (9)
Scott McCulloch (1)
Scott Schlesier (11)
Scott Wilkinson (3)
Scott Willhite (97)
Sebastian Leupold (80)
Shaun Walker (237)
Shawn Mehaffie (17)
Stefan Cullmann (12)
Stefan Kamphuis (12)
Steve Fabian (31)
Steven Fisher (1)
Tony Henrich (3)
Torsten Weggen (3)
Tycho de Waard (4)
Vicenç Masanas (27)
Vincent Nguyen (3)
Vitaly Kozadayev (6)
Will Morgenweck (40)
Will Strohl (180)
William Severance (5)
What is Liquid Content?
Find Out
What is Liquid Content?
Find Out
What is Liquid Content?
Find Out