Sunday, 24 October 2010

Designing SharePoint applications for Unit Testing

With the release of Microsoft Moles isolation framework (http://research.microsoft.com/en-us/projects/moles/) SharePoint developers finally have a free tool for mocking (or as we now call it: moling) calls into the SharePoint object model, which has been historically very difficult (because of sealed classes that can't be mocked, no interface implementation etc).
Testing your SharePoint components via the Framework is easy enough for components that can be isolated, however that is more difficult for components that have UI code dependencies, such as Visual Web Parts and Custom Controls.

This is no different from the isolation challenges of a .Net application where Controls need to have the business logic separated from the UI in order to enable unit testing the business logic itelf with the test mocking interactions with the UI.

This article provides one way of solving separation of concerns in SharePoint components dependent on UI. This is using the MVP pattern to separate UI code in Views and component logic in Presenters. In another article (soon to come) I will demo writing unit tests for these Presenters.
I won't go into what MVP Patterns is, there are plenty of resources online. This MSDN article is a good first point of reference http://msdn.microsoft.com/en-us/library/ff647543.aspx

This solution also uses the Autofac Dependency Injection (DI) container to wire Presenter to View in each control. http://code.google.com/p/autofac/
You can, however, use another DI container of your choice such as Microsoft's Unity, Castle Windsor and more. I won't go into DI container comparisson here, there are resources online to help you make that choice, one article that compares them is here http://blog.ashmind.com/index.php/2008/08/19/comparing-net-di-ioc-frameworks-part-1/
The reasons I use Autofac is that it provides a lightweigh and fast IoC framework that keeps necely to the side and doesn't introduce unneccessary dependencies in code.

So let's get started!

1. Wire the DI container to the SharePoint application


Extend the SPHttpApplication that your SharePoint application runs under to instantiate the DI Container. The extended SPHttpApplication, named SampleSPHttpApplication in the example below will maintain a container provider for injecting dependencies into web requests. SampleSPHttpApplication needs to implement IContainerProviderAccessor which will provision the Container to the application.

 public class SampleSPHttpApplication : SPHttpApplication, IContainerProviderAccessor
{
private static object _lockObject = new object();
private static IContainerProvider _containerProvider;
/// <summary>
/// Setup container on application start.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void Application_Start(object sender, EventArgs e)
{
if (ContainerProvider == null)
{
lock (_lockObject)
{
if (ContainerProvider == null)
{
ContainerProvider = BootStrapper.CreateContainerProvider();
}
}
}
}
/// <summary>
/// Gets hold of the container.
/// </summary>
public IContainerProvider ContainerProvider
{
get { return _containerProvider; }
protected set { _containerProvider = value; }
}
}

I have a Bootstrapper helper class that does the Presenter registrations and creates the container provider.

 public class BootStrapper
{
public static ContainerProvider CreateContainerProvider()
{
var containerBuilder = new ContainerBuilder();
RegisterPresenters(containerBuilder);
var containerProvider = new ContainerProvider(containerBuilder.Build());
return containerProvider;
}
private static void RegisterPresenters(ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<SiteProvisioningPresenter>().PropertiesAutowired().InstancePerDependency();
}
}

The Presenter registration section of the Bootstrapper allows you to register your presenter classes and their dependencies such as Model and Services. Those will then be autowired when your View instantiates its Presenter.

Finally, hook the new SPHttpApplication in the global.asax by updating the Application to reference your custom SPHttpApplication.

 <%@ Assembly Name="SampleApp"%>
<%@ Application Language="C#" Inherits="SampleApp.Modularity.SampleSPHttpApplication" %>
This step has effectively wired the DI container in the SharePoint application and we can now use it to resolve presenters.

2. Build the base View/Presenter framework

While the previous section dealt with components inside your application, the ones in this section are generic enough to be grouped into a separate Project that can be reused accross applications. I have grouped these in an Avanade.Modularity namespace, diagram off Visual Studio of that solution is below.



This is a model I adapted from a large (non SharePoint) eCommerce project I worked on last year which is largely modelled by Ben Taylor but which

View Interface
This provides the contract for your UI controls. ViewLoaded event is fired in your control when it loads, providing a hook into OnLoad that the Presenter can use to perform logic in that part of the control lifecycle. My sample presenter uses this to set the values of a dropdown list on the view.
 public interface IView
{
event EventHandler<ViewLoadedEventArgs> ViewLoaded;
}

Base Presenter

 public interface IPresenter
{
}
public interface IPresenter<TView> : IPresenter
{
}
This is what all your presenters inherit from. It simply attaches to the ViewLoaded even from the view allowing you to interact with your control during its initiation.
  public abstract class BasePresenter<TView> : IPresenter<TView> where TView : IView
{
private TView _view;
public virtual TView View
{
get { return _view; }
set
{
_view = value;
_view.ViewLoaded += (s, e) => OnViewLoaded(e);
}
}
protected abstract void OnViewLoaded(ViewLoadedEventArgs e);
}

Base Control
This will be the base class for your user controls, whether those are Visual Web Parts or custom controls. We extend the UserControl class to provide the view with the relevant presenter and its dependencies as registered in Bootstrapper (see previous section for Bootstrapper registration details)
 abstract public class ViewControl<TView, TPresenter> : UserControl
where TPresenter : BasePresenter<TView>
where TView : class, IView
{
public TPresenter Presenter { get; set; }
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
var accessor = Context.ApplicationInstance as IContainerProviderAccessor;
if (accessor == null || accessor.ContainerProvider.ApplicationContainer == null) throw new InvalidOperationException("Cannot find Autofac container");
var container = accessor.ContainerProvider.ApplicationContainer;
Presenter = container.Resolve<TPresenter>();
Presenter.View = this as TView;
//if (!this.IsPostBack)
//{
// ViewLoaded.Raise(this, new ViewLoadedEventArgs(true));
//}
}
}

View Loaded event Arguments
This allows the View to tell the Presenter whether this is the first time it loads. This is useful when you need to perform actions only on first load on the control.
 public class ViewLoadedEventArgs : EventArgs
{
public bool IsFirstLoad { get; private set; }
public ViewLoadedEventArgs(bool isFirstLoad)
{
IsFirstLoad = isFirstLoad;
}
}

Event Handler Extensions
 public static class EventHandlerExtensions
{
public static void Raise<T>(this EventHandler<T> eventHandler, object sender, T args) where T : EventArgs
{
if (eventHandler != null)
eventHandler(sender, args);
}
public static void Raise(this EventHandler eventHandler, object sender, EventArgs args)
{
if (eventHandler != null)
eventHandler(sender, args);
}
}


3. Start building your MVP Controls!

You can now start creating your Controls and Visual Web Parts using the framework. The Visual Web Part below is a simple site provisioning control which enumerates the available Site templates and allows you to create a site based on a selected template. (I chose this scenario as it has a deep reach in the SP object model to demonstrate moling a call for this in my next post).

After creating your control you need to inherit from our base ViewControl.


Visual Web Part Control
 public partial class SiteProvisioningUserControl : ViewControl<ISiteProvisioningView, SiteProvisioningPresenter>, ISiteProvisioningView
{
protected void Page_Load(object sender, EventArgs e)
{
ViewLoaded.Raise(this, new ViewLoadedEventArgs(!IsPostBack));
btnCreateSite.Click += CreateSiteRequested;
}
public IEnumerable<string> AvailableSiteTemplates
{
set
{
ddlSiteTemplate.DataSource = value;
ddlSiteTemplate.DataBind();
}
}
public string SiteTitle
{
get { return txbSiteTitle.Text; }
}
public string SiteTemplate
{
get { return ddlSiteTemplate.SelectedValue; }
}
public event EventHandler<ViewLoadedEventArgs> ViewLoaded;
public event EventHandler CreateSiteRequested;
}

The site provisioning view implements the base IView and exposes all the UI elements the Presenter needs to interact with, such as setting the AvailableSiteTemplates dropdown list values and getting the user defined SiteTitle.
 public interface ISiteProvisioningView : IView
{
event EventHandler CreateSiteRequested;
IEnumerable<string> AvailableSiteTemplates { set; }
string SiteTitle { get; }
string SiteTemplate { get; }
}

The site provisioning presenter handles the events from the View such as the ViewLoad and the user clicking on button to create a new site.
 public class SiteProvisioningPresenter : BasePresenter<ISiteProvisioningView>
{
protected override void OnViewLoaded(ViewLoadedEventArgs e)
{
View.CreateSiteRequested += new EventHandler(View_CreateSiteRequested);
if (e.IsFirstLoad)
{
// Load available web templates and set them on the view.
SPWebTemplateCollection availableTemplates = SPContext.Current.Site.GetWebTemplates(Convert.ToUInt32(1033));
if (availableTemplates != null)
View.AvailableSiteTemplates =
availableTemplates.Cast<SPWebTemplate>().Select(availableTemplate => availableTemplate.Name).
AsQueryable();
}
}
void View_CreateSiteRequested(object sender, EventArgs e)
{
using (var rootWeb = SPContext.Current.Site.RootWeb)
{
var siteTitle = View.SiteTitle;
var siteTemplateName = View.SiteTemplate;
var clientSite = rootWeb.Site.AllWebs.Add(siteTitle, siteTitle, "Provisioned by Custom Process", 1033, siteTemplateName, false, false);
SPUtility.Redirect(clientSite.Url, SPRedirectFlags.Default, HttpContext.Current);
}
}
}

Below is an example of what my solution structure looks like. The Modularity project comprises of all base interface and classes and the SampleApp SharePoint Project which includes my SPHttpApplication and BootStrapper logic as well as the rest of my application.

























In another post I will cover unit testing the Presenters using Microsoft Moles framework and MSTest.

No comments:

Post a Comment