Introducing SharePoint Emulators

The Visual Studio team is pleased to announce availability of Visual Studio 2012 SharePoint Emulators. Unit testing for SharePoint code is difficult. Ideally you would like to isolate code under test from the surrounding SharePoint framework. Isolation frameworks, such as Microsoft Fakes, can enable this code isolation. Unfortunately, as anyone who has followed this path will tell you, creating and maintaining the set of mocks and fakes for the SharePoint environment can be very costly. Luckily, the SharePoint Emulators team has done the heavy lifting for you. SharePoint Emulators provide a system of Fakes based shims implementing the basic behaviors of the SharePoint 2010 server object model.

System Requirements

· Microsoft SharePoint 2010 Server (SharePoint 2010 Server or SharePoint 2010 Foundation)

· Microsoft Visual Studio 2012 Ultimate

Getting SharePoint Emulators

SharePoint Emulators are available today through a NuGet package feed. Perform a simple search of the NuGet official feed for “SharePoint Emulators”.

clip_image001

Using SharePoint Emulators

SharePoint Emulators are easy to incorporate into existing tests. You can write tests against the SharePoint Emulators using only the SharePoint API. All that is needed is the wrapping of test code in a SharePointEmulationScope.

[TestMethod]     
public  void ScheduleAppointmentReturnsTrueWhenNewAppointmentIsCreated()     
{     
    using (new SharePointEmulationScope(EmulationMode.Enabled))     
    {     
        SPSite site = new SPSite("
http://localhost");   
        string listName = String.Format("List{0}", Guid.NewGuid());

        // create a new temporary list     
        Guid listId = site.RootWeb.Lists.Add listName, listName, 
                      SPListTemplateType.GenericList);
     
       
SPList list = site.RootWeb.Lists[listId];     
        Assert.IsNotNull(list);

        // add fields to list     
        list.Fields.Add("Name", SPFieldType.Text, true);     
        list.Fields.Add("Phone", SPFieldType.Text, false);     
        list.Fields.Add("Email", SPFieldType.Text, false);     
        list.Fields.Add("Age", SPFieldType.Text, false);     
        list.Fields.Add("Date", SPFieldType.Text, false);
        list.Update();

        // prepare
        string errorMsg = string.Empty;
        DateTime date = DateTime.Now;
        BookAnAppointmentWebPart webPart = new BookAnAppointmentWebPart();

        // act
        bool success = webPart.ScheduleAppointment(site.RootWeb, list.Title, "My Name",
                       "888-888-8888", "My.Name@contoso.com", "23", date, out errorMsg);

        // assert
        Assert.IsTrue(success);

        // cleanup
        list.Delete();
        site.Dispose();
    }
}

All code wrapped within a SharePointEmulationScope will be automatically detoured at runtime. All calls against the Microsoft.SharePoint.dll assembly will be rerouted to the SharePoint Emulators.

Extending SharePoint Emulators

Due to the wide size and scope of the SharePoint API surface we focused on the types and methods most highly used. Using a type that has not been implemented results in a little bit of extra work to get the test passing.  As an example the following test fails, with an unimplemented exception.

[TestMethod]
public void GetAppointmentsForTodayReturnsTwoAppointments()
{    
    using (var emulationScope = new SharePointEmulationScope(EmulationMode.Enabled))    
    {    
        SPSite site = new SPSite("
http://localhost");  
        string listName = String.Format("List{0}", Guid.NewGuid());

        // create a new temporary list    
        Guid listId = site.RootWeb.Lists.Add(listName, listName,    
        SPListTemplateType.GenericList);     
        SPList list = site.RootWeb.Lists[listId];     
        Assert.IsNotNull(list);

        // add fields to list     
        list.Fields.Add("Name", SPFieldType.Text, true);     
        list.Fields.Add("Phone", SPFieldType.Text, false);     
        list.Fields.Add("Email", SPFieldType.Text, false);     
        list.Fields.Add("Age", SPFieldType.Text, false);     
        list.Fields.Add("Date", SPFieldType.Text, false);     
        list.Update();

        DateTime date = DateTime.Now;     
        BookAnAppointmentWebPart webPart = new BookAnAppointmentWebPart();

        // insert 3 item into list     
        SPItem item = list.Items.Add();     
        item["Name"] = "My Name";     
        item["Phone"] = "888-888-8888";     
        item["Email"] = "My.Name@contoso.com";     
        item["Age"] = "23";     
        item["Date"] = date.ToString("D");     
        item.Update();

        SPItem item2 = list.Items.Add();     
        item2["Name"] = "His Name";     
        item2["Phone"] = "777-777-7777";     
        item2["Email"] = "His.Name@contoso.com";     
        item2["Age"] = "25";     
        item2["Date"] = date.AddDays(1).ToString("D");     
        item2.Update();

        // Act     
        string result = webPart.GetAppointmentsForToday(list.Title, site.RootWeb);

        // Assert     
        Assert.IsTrue(result.Contains(String.Format("Name: My Name, Phone: 888-888-8888,
               Email: My.Name@contoso.com, Age: 23, Date: {0}", date.ToString("D"))));    
        Assert.IsFalse(result.Contains("Name: His Name"));

        // cleanup    
        list.Delete();    
        site.Dispose();    
    }    
}

 clip_image003

 

If you do find an area not covered, it is straightforward to extend and implement new emulated behavior. SharePoint Emulators rely entirely on the Microsoft Fakes framework. They consist of only concrete implementations of shims. When encountering an unimplemented shim all that is needed is to provide a implementation of that shim. The details of the shim that is required is detailed in the error message, in this example SPList.GetItems(SPQuery).

if (emulationScope.EmulationMode == EmulationMode.Enabled)    
{    
    var sList = new ShimSPList(list);    
    sList.GetItemsSPQuery = (query) =>    
    {    
        var shim = new ShimSPListItemCollection();    
        shim.Bind(new[] { list.Items[0] });    
        return shim.Instance;    
    };    
}

The addition of this code to the previous test allows it to pass. You can use this technique to implement missing behavior or to customize Emulators to match your own environment.

Running Multi-Target Tests

Running tests against the Emulators provides many advantages: performance improvements, isolation from SharePoint, and reduced maintenance of test SharePoint infrastructure. However, there are times when you still want to run tests against a real SharePoint server. SharePoint emulators enables the reuse of the same test code, but run in different context.

The SharePointEmulationScope takes as a parameter an optional EmulationMode enum. The default enabled mode performs run time interception of all Microsoft.SharePoint.dll calls. The second passthrough value allows all calls to bypass all shims and directly call into the original assembly. The use of this enum value can control the context of test execution.

There are numerous ways of consuming and controlling this EmulationMode value. The SharePoint emulator team is fond of Data Driven tests to control the EmulationMode. In this way the test team is able to write tests that work against the SharePoint Emulators and the default SharePoint server. We use these tests and this approach to verify the behavior of the SharePoint Emulators. There are lots of alternatives for usage: setup and initialization code, preprocessor directives, ect.

 

Resources