One of the primary demotivators that I've been encountering among SharePoint developers when it comes to unit testing their code is how to do it easily. TDD is a conceptual stretch for most folks to begin with, never mind the complexities that can arise when testing code that has to run under a secure web app framework like SharePoint.
So, when I was tapped last fall to create an application to migrate documents into a SharePoint 2003 server taxonomy, I took the opportunity to stop the kvetching, get down to brass tacks and write some tested code! In this post, I'll explain how I used the NUnit test suite to build a sophisticated SharePoint test harness.
To keep things brief, I'll be assuming that the reader is familiar with SharePoint development and NUnit test authoring.
Getting Started: The Development Environment
Here at the office we use Microsoft Virtual Server images to host a development instance of Windows 2003 with WSS and SPS installed. Generally speaking, we have a solution set up in VSS with post-build event scripts configured for each project that copies the compiled binaries to the server image via a mapped network drive -- we use these after doing the initial deployment of web part packages with the attendant stsadm -o addwppack rigamarole.
As part of a test-ready development server image, I also install NUnit to the server since the test assemblies need to be run under the SharePoint app domain. Now, I've noticed that there are remoting methods built-in to NUnit's core framework, and someday I hope to migrate to a model where I can run the test gui locally -- for now, I'm more interested in writing tests.
The Evolution of the BaseSharePointUnitTestFixture
At first, my NUnit test classes weren't very elegant -- they were designed to test the functionality of a class under development. As such, there was a lot of common functionality that I began to notice as the test classes evolved. For example, I found that I was frequently using SPSite, SPWeb, PortalSite, PortalContext and TopologyManager objects to test my assemblies, as well as attendant methods for setting them up. These were moved into the base class:
1 ///
2 /// This class defines an NUnit compatible test fixture assembly for
3 /// running unit test methods.
4 ///
5 [TestFixture]
6 public class BaseSharePointUnitTestFixture
7 {
8 protected string _baseSiteUrl = "";
9 protected string _webUrl = "";
10 protected SPSite _spSite = null;
11 protected SPWeb _spWeb = null;
12
13 protected string _portalUrl = "";
14 protected SPSite _spPortalAreaSite = null;
15 protected SPWeb _spPortalAreaWeb = null;
16 protected PortalSite _spPortalSite = null;
17 protected PortalContext _spPortalContext = null;
18 protected TopologyManager _spTopologyMgr = null;
19 protected Area _spTargetArea = null;
20 protected Guid _spTargetAreaGuid;
Notice that I scoped the members as protected so as to allow descendant classes easy access to them. I then added methods such as InitializeSPObjects for simple initialization:
21 ///
22 /// Initializes the test fixture's SPSite and SPWeb objects using the URLs specified
23 /// by the _baseSiteUrl and _webUrl properties.
24 ///
25 protected void InitializeSPObjects()
26 {
27 _spSite = GetSPSiteAreaObject(_baseSiteUrl);
28
29 if(_webUrl != null && _webUrl.Trim().Length > 0)
30 spWeb = _spSite.OpenWeb(_webUrl);
31 else
32 _spWeb = _spSite.OpenWeb();
33 }
Impersonation Support
Often, when running test code you may find yourself unable to perform certain tasks against the SharePoint object model because you do not possess sufficient rights. Sometimes, the assemblies under test require some level of authentication agnosticism. Whatever the motivation, you're going to need to run code under the credentials of the service account -- I added this functionality to my base class by referencing the System.Runtime.InteropServices, System.Security.Principal and System.Security namespaces and adding some methods and Win32 kernel references:
34 protected static WindowsIdentity _connectedUser = null;
35 protected static WindowsImpersonationContext _impersonatedServiceAccount = null;
36 [DllImport("advapi32.dll")]
37 protected static extern int RevertToSelf();
...
38 ///
39 /// Starts running current thread under the service account identity.
40 ///
41 protected static void StartRunAsServiceAccount()
42 { 43 _connectedUser = WindowsIdentity.GetCurrent();
44 RevertToSelf();
45 _impersonatedServiceAccount = WindowsIdentity.GetCurrent().Impersonate();
46 }
...
47 ///
48 /// Stops running the current thread under the service account identity.
49 ///
50 protected static void StopRunAsServiceAccount()
51 { 52 if(_impersonatedServiceAccount != null)
53 _impersonatedServiceAccount.Undo();
54
55 if(_connectedUser != null);
56 _connectedUser.Impersonate();
57 }
Using these methods, I can easily bracket some secure method calls with StartRunAsServiceAccount and StopRunAsServiceAccount where needed. This is really useful when stashing things into a document library or setting up tests using the DirectoryServices namespace for interacting with ActiveDirectory.
Now for something controversial...
A significant cause of debate in the TDD world is whether test assemblies should be allowed to violate the principle of encapsulation for assemblies under test. In other words, should we be able to access private properties and methods.
Look: I won't get pious on you. I believe in good OO design as much as the next guy, but sometimes being able to check the state of a private member or assert against a private method serves a purpose in setting up a test case (or tearing one down). To that end, I added two methods that I modded from a blog entry by Arne Vandamme (see my 12/07/05 blog entry) that allows for accessing private methods and properties:
58 ///
59 /// Returns the value of an object's private member field.
60 ///
61 ///
62 ///
63 ///
64 protected object GetPrivateField(object instance, string fieldName)
65 {
66 Type t = instance.GetType();
67 FieldInfo f = t.GetField(fieldName,
68 BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
69
70 return f.GetValue(instance);
71 }
...
72 ///
73 /// Invokes a private method for an object, returning any values or objects.
74 ///
75 ///
76 ///
77 ///
78 ///
79 protected object ExecutePrivateMethod(object instance, string methodName, params object[] paramList)
80 { 81 Type t = instance.GetType();
82
83 if(paramList != null)
84 { 85 Type[] paramTypes = new Type[paramList.Length];
86 for(int i=0; i < paramList.Length; i++)
87 paramTypes[i] = paramList[i].GetType();
88
89 MethodInfo m = t.GetMethod(methodName,
90 BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
91 null,
92 paramTypes,
93 null);
94
95 return m.Invoke(instance, paramList);
96 }
97 else
98 { 99 MethodInfo m = t.GetMethod(methodName,
100 BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
101
102 return m.Invoke(instance, null);
103 }
104 }
These are really easy to use: Just pass in an instance of the test object, the method name you want to invoke and parameters and you're off to the races. As I mention, this is really useful for reaching in and checking the state of your objects when no other means is available. Yes, it's bad, but not as bad as the kvetching about it and not doing any TDD because it's holding up your state of mind.
Cleaning things up
As good developers, we're always wanting to make sure that we clean up after ourselves, and this base class is no exception. It's always good form to include some methods for closing off our SharePoint objects so as to not tie them up unnecessarily:
105 ///
106 /// Releases the objects used for testing the portal.
107 ///
108 protected void ClosePortalObjects()
109 {
110 if(_spPortalAreaWeb != null)
111 _spPortalAreaWeb.Close();
112
113 if(_spPortalAreaSite != null)
114 _spPortalAreaSite.Close();
115
116 _spPortalAreaWeb = null;
117 _spPortalAreaSite = null;
118 _spPortalSite = null;
119 _spPortalContext = null;
120 _spTargetArea = null;
121 _spTopologyMgr = null;
122 }
...
123 ///
124 /// Closes the SPSite and SPWeb member objects.
125 ///
126 protected void CloseSPObjects()
127 { 128 if(_spWeb != null)
129 _spWeb.Close();
130 if(_spSite != null)
131 _spSite.Close();
132 }
Summary
In today's post I've provided a brief overview of how I approach unit testing SharePoint applications via the construction of an NUnit test fixture base class that serves as the foundation for descendant test classes. This base class aggregates common methods and properties that make writing test cases a lot easier for developers, removing one of the chief mental blocks when it comes to unit testing SharePoint code.
In an upcoming post, I'll show how to use this class to construct some basic unit tests for interacting with the SharePoint object model. For now, I welcome any and all feedback. How are you handling TDD with SharePoint or ASP.NET? Let's share the knowledge!
Download sample code for this article.