A couple of weeks ago Jan Tielens released the SmartPart version 1.0.0.0. The SmartPart is a very cool WebPart that will host ASP.Net user controls in a SharePoint environment. This means you can use Visual Studio to edit the user controls and use the WYSIWYG editor instead of building the HTML via code. Additionally, Maxim Karpov helped out with the CAS aspect of the SmartPart which can be a confusing topic if you're not Maxim. Maxim does a great job of explaining the CAS issues and even though I've worked with CAS for things like custom code in reporting services, I still feel it is something foriegn (I think CAS knowledge seeps out of the brain).
So the SmartPart allows you do quickly create SharePoint WebParts and you can even debug those WebParts in a normal ASP.Net application, but what about using Test Driven Development (TDD)?
Well Scott Hanselman gave me an idea that I thought would be worth trying out. Scott detailed how to use TDD for normal ASP.Net development using Cassini, an idea I found to be very cool. So could I use the same technique to use TDD for SmartParts? The answer is you can.
First I created a SmartPart project to build my sample SmartPart. To do this I used a regular class library project since this will give me everything I need without having to use a web server. You will need to add references to System.Web, Microsoft.SharePoint, and SmartPart. For my simple SmartPart I created one ASCX file and one CS file. In my user control I added a Label and in the Page_Load event handler I set the Text property to “This is a simple smart part.”.
The next step is to create your testing project. Again I created a class library project and this time added references to the Cassini (using the cassini.dll) and NUnit.Framework (I'm using NUnit 2.2). I had to make some modifications to Scott's code in order to make things work since my approach is slightly different. He is testing ASPX pages while I'm testing a user control that needs to be loaded in an ASPX page.
The first test was just to start the cassini server and open an ASPX page that had no control to make sure everything worked. That was fairly easy thanks to Scott. The next step was a little more involved since I had to copy the user control and the dll from my other project and reference the ASCX in the ASPX page. However, after some trial and error I got that working as well. The real fun came when I went to set the SPWeb property of my SmartPart as shown by Jan in his debugging post.
The SPGlobalAdmin class requires the Microsoft.SharePointLibrary.dll in order to function (which exists only in the GAC) [Update: I made a change here since I made a mistake in the original post. The SPGlobalAdmin is in the Microsoft.SharePoint.Administration namespace, not library.]. I remembered seeing a post about how to extract this from the GAC so after a little googling I came across this example which allowed me to extract the dll and add it to the bin folder of my cassini project. Once I had this dll accessible to cassini things started to work except for a security exception: Failure decoding embedded permission set object. Hmmm.
This one kept me baffled for quite some time until I got the bright idea this morning (after reading some of Maxim's CAS articles again) to take a look at the constructor for the SPGlobalAdmin class using Reflector. Using Reflector I noticed that the contructor demanded the permission Microsoft.SharePoint.Security.SharePointPermission. Ahhh. So back to the GAC again to get Microsoft.SharePoint.Security.dll.
Once I had that everything worked. All my tests passed and I'm able to start developing my SmartParts using TDD. Now I'm off to see if I can figure out how to test creating and using connections. If people are interested I can write a short article with the code I used to do this.
Update: I ended up running into issues when setting the SPWeb property. I was able to get the SPGlobalAdmin but nothing past that. But at this point I'm not sure how much that matters. I did get some very simple connections working just by using Part1.SetConsumerData(Part2.GetProviderData()).