Partager via


Unit testing SharePoint code

In some projects lately I've been faced with the question about how to unit-test SharePoint code.

I've been experimenting with unit testing in Visual Studio 2008 and particularly testing SharePoint code.

What do I mean by "SharePoint code" ? I mean server-side .NET code written against the SharePoint API (WSS 3.0 or MOSS assemblies, most notable the Microsoft.Sharepoint.dll).

There are of course several ways to code against SharePoint. You can establish the SharePoint context (which site, page, user etc. in SharePoint) completely from within your code as this example shows:

public static string GetSiteTitle(string url)
{
    SPSite site = new SPSite(url);
    string title = site.RootWeb.Title;
    site.RootWeb.Dispose();
    site.Dispose();
    return title;
}

This code does not require a SharePoint context to run. That means that you don't need to run this code trough a SharePoint site/page, webpart or service. It can easily be invoked from a windows forms or console app. This kind of SharePoint code can easily be unit tested just like any other code.

Another way to code against SharePoint is to use the SharePoint context object. Whenever code is invoked on a page, control or webpart within a SharePoint site (using ASP.NET/IIS), the SharePoint context is available - like the HTTP Context. The SharePoint context contains information about the current site, page, user, etc. To get the title of the current SharePoint site using the SharePoint context, you can use the following code:

public static string GetSiteTitle()
{
    SPSite site = SPContext.Current.Site;
    string title = site.RootWeb.Title;
    return title;
}

This method must be invoked through a SharePoint site because it uses the SPContext object. If you try to invoke the code from a console or windows forms application, you will get an exception because SPContext is null.

So, how can the code be unit tested in Visual Studio when you're not able to invoke the method without the SharePoint context? The answer is to bring the context to Visual Studio!

The usual way to implement a unit test would be something like this:

[TestMethod()]
public void SayHelloTest()
{

  string url = https://localhost/site;
  string expected = "Value";
  string actual;
  actual = MyClass.GetSiteTitle(url);
  Assert.AreEqual(expected, actual);

}

If you create a test that calls a method which is using SPContext, you will get an exception in the test result. This is an example of two tests calling methods which is not using the SharePoint context, and one that is:

image

To be able to unit test code using the SharePoint context, you can hook up the host adapter for ASP.NET in the unit test. This is an example:

[TestMethod()]
[HostType("ASP.Net")]
[UrlToTest("https://localhost/default.aspx")]
public void SayHelloMossTest()
{
  string input = "MOSS"; 
  string expected = "Hello MOSS from site Home"; 
  string actual;
  actual = SayMOSS.SayHelloMoss(input);
  Assert.AreEqual(expected, actual);

}

After adding HostType and UrlToTest, the test is invoked using the host adapter for ASP.NET. Running the tests then would be able to host the SharePoint context as well as the ASP.NET HTTP Context:

image

Another way to use the ASP.NET host adapter for ALL tests is to specify it in the test configuration:

Go to Test -> Edit Test Run Configuration and select a configuration.

Select "Hosts"

image

Now, when running tests, all tests will be executed with the ASP.NET host adapter.

You can also use the SharePoint context directly in the test like this example shows:

[TestMethod()]
[HostType("ASP.Net")]
[UrlToTest("https://localhost/default.aspx")]
public void GetContextInfo()
{
    string expected = "domain\\user";
    string name = Microsoft.SharePoint.SPContext.Current.Web.CurrentUser.LoginName; // get the login name of the current user
    Assert.AreEqual(name, expected);
}

So, this definitely shows that it is possible to unit test SharePoint code, however there will be some limitations:

  1. You cannot execute tests against remote servers. This means that SharePoint must be installed on the machine running the tests. This might be the case for developer workstations, but might not be the case for build server(s).
  2. Code needs to run with full trust. You need to enable full trust in web.config for the SharePoint web application
  3. Debugging not as easy when using host adapter. See more below.
  4. Tests are invoked with the current logged on user in Visual Studio or the service account for msbuild. I haven't found a way to execute tests against SharePoint with a particular users credentials.

What about debugging?

Since the test code invoked using the ASP.NET host adapter runs in the host adapter, you cannot simply add a breakpoint in the test code and expect it to break when running the test. To be able to debug hosted tests use the following procedure:

1. Add a breakpoint in the code

2. Select Debug -> Attach to process

3. Attach to the w3wp.exe process

4. Instead of hitting F5 or clicking the "Run" button (the green arrow icon) to run the test, select "Test" -> "Run"  in the main menu. This will run the tests and the debugger will be invoked on a breakpoint using the host adapter.

Comments

  • Anonymous
    October 07, 2008
    PingBack from http://www.easycoded.com/unit-testing-sharepoint-code/

  • Anonymous
    October 09, 2008
    Top News Stories Why Security Pros Hate SharePoint, and What to do About it (NetworkWorld) Microsoft

  • Anonymous
    October 22, 2008
    Great post Tommy. You might want to include a note for LHS aka W2k8 users. If they want to use Visual Studio Unit tests on LHS they have to turn off the UAC (User Acount Control) service, which is turned on by default...

  • Anonymous
    November 15, 2008
    Tommyo, This is a good article, however I have to disagree with your approach.  Unit testing should not hit the SharePoint infrastructure - this makes the tests integration tests rather than unit tests and also requires each developer to have a specific SharePoint configuration installed on their machines.  You will find that your tests will be slow to run and will also end up with dependencies on the order they are run.  I much prefer the make use of Mocking frameworks to fake the SharePoint calls,  after all we are not actually wanting to test the SharePoint API, we want to test our logic. I have written a couple of white papers on this that I think you will be very interested in - both can be found under the unit testing section at http://www.21apps.com/agile/ http://www.21apps.com/agile/beginners-guide-to-test-driven-web-part-development/ and http://www.21apps.com/agile/unit-testing-sharepoint-getting-into-the-object-model/ Andrew

  • Anonymous
    November 16, 2008
    The comment has been removed

  • Anonymous
    November 23, 2008
    Hi Tommyo! After I set the Test Run Configuration and run tests, I get "The Web request 'http://localhost/' completed successfully without running the test." error message. Can you help? Any suggestion is grateful.

  • Anonymous
    November 27, 2008
    My colleague Tommy wrote a post about unit testing SharePoint applications a few weeks ago. I must admit

  • Anonymous
    February 03, 2009
    Great post Tommy. I found some more useful information on unit testing sharepoint in Typemocks Sharepoint page http://www.typemock.com/sharepointpage.php

  • Anonymous
    February 12, 2009
    My company is going to start unit testing Sharepoint, would you recommend using a mock framework ? If yes, which one would be best ? Thanks in advance for any answer ...

  • Anonymous
    November 06, 2009
    heres a trick to invoke any sharepoint methods as someone else (network/webservice/filesystem methods will still need true impersonation) public void SetSPContext(string loginName) {  using (SPSite currentSite = SPContext.Current.Site)  using (SPWeb currentWeb = SPContext.Current.Web)  {      SPSite site = new SPSite(currentSite.ID, GetUserToken(loginName));      context.Items["HttpHandlerSPSite"] = site;      context.Items["HttpHandlerSPWeb"] = site.OpenWeb(currentWeb.ID);      context.Items["DefaultSPContext"] = null;  } } public SPUserToken GetUserToken(string loginName) {    using (SPSite site = new SPSite(SPContext.Current.Site.ID, SPContext.Current.Site.SystemAccount.UserToken))    using(SPWeb web = site.OpenWeb(SPContext.Current.Web.ID))        return web.EnsureUser(loginName).UserToken; }