SPEmulators available on nuget

Every SharePoint developer that does TDD is desperately waiting for Microsoft to release a Version of the Microsoft.SharePoint.Emulators for SharePoint 2013. Unfortunately it seems that Microsoft does not have any plans to publish a new release in the near future. This is completely incomprehensible since there are a lot of SP2010 solutions that must be migrated to SP2013. To close the gap there is now the the nuget package SPEmulators. The source code is available in a repository on GitHub.

Installation

To install SPEmulators, run the following command in the Package Manager Console: Install-Package SPEmulators. You can also search for the package in “Manage nugget packages…” and click “install”.

Note: the latest stable build was created against the .net 4.5.1 (see the comments of this thread). If you want to use the package with .net 4.5 you can use the prerelease version.

image

Creating the first tests

To use the SPEmulators framework you have to create the SPEmulationContext. The context is disposable and must be disposed after your test. You can do this in a TestCleanup-Method or within a using statement.

using (var context = new SPEmulationContext(IsolationLevel.Fake))
{
    //...
}

IsolationLevel

The constructor with one argument of the type IsolationLevel is for compatibility with the Microsoft.SharePoint.Emulators. The EmulationMode Enabled maps to the IsolationLevel Fake. The emulationMode Passthrough maps to the IsolationLevel None.

For new code you can use the constructor that takes to argument: the IsolationLevel and the url.

IsolationLevel Description
Fake Indicates, that tests run against a faked SharePoint API. The code runs against sharepoint objects that do not exist and are very fast. The context provides you with a Web and a Site property that can be used to access the current web. The context also takes care of the ShimsContext. Use this mode to run fast UnitTests.
Integration Indicates, that tests run against the SharePoint API as integration tests. The shim context will be initialized and the SPWeb and SPSite objects will be added to a fake SPContext.

The test run against a real SharePoint web that is injected in the SPContext using Shims. Make sure to pass in a valid url and that the required lists exist in the site. The tests are slow and should be used for integration tests.

None Indicates, that tests run against the SharePoint API without a shim context. No SPContext will be initialized.

This is for compatibility. This mode should only be used for tests that would also run without SPEmulators.

The context takes care of creating the web and site objects and injecting them to the spcontext in integration mode. You can access the web and site thru properties of the context.

using (var context = new SPEmulationContext(IsolationLevel.Fake, "http://localhost/"))
{
    var web = context.Web;
    var site = context.Site;

    // ...
}

If you want to get a list in integration mode you can access it using the web property:

var list = context.Web.Lists[name];

If you are in fake mode you have to add the list first to the fake web:

var id = context.Web.Lists.Add(name, string.Empty, type);
var list = context.Web.Lists[id];

Since working with lists is the most common task in SharePoint the context already as a function for you that does this depending of the isolation level. You can use the function GetOrCreateList of the context to access lists in tests.

var list = context.GetOrCreateList("ListName", SPListTemplateType.GenericList, "Fieldname1", "Fieldname2");

The function takes the list name and type as parameters. You can also pass in an optional array of field names. If the list is generated in fake mode the fields will already be added (as simple text fields!). This will not cover all scenarios. You might have to create the list on your own.

Performance

The performance varies depending of the selected isolation level. The same test might only take a view milliseconds in isolation level fake that takes some seconds in isolation mode integration.

Best practices

If you plan to use the same tests as unit and integration tests, its best practice to store the isolation level and url in the app settings file. This way you can easily configure all your tests by simply deploying an app.config along with your tests.

 

The tests

Writing the tests is not different from writing tests with the Microsoft.SharePoint.Emulators. There is a lot of documentation and sample source on msdn and other blogs. I won’t cover this here in more detail. I just add a sample, how a test class could look.

[TestClass]
public class CustomerListRepositoryTest
{
    SPEmulationContext context;
    SPList customerList;
    CustomerListRepository repository;

    [TestInitialize]
    public void TestInitialize()
    {
        context = new SPEmulationContext(Settings.Default.IsolationLevel, Settings.Default.Url);
        customerList = context.GetOrCreateList(CustomerListRepository.ListName, SPListTemplateType.GenericList);
        repository = new CustomerListRepository();
    }

    [TestMethod]
    public void CanAddCustomer()
    {
        var itemCount = customerList.ItemCount;
        repository.Add(new Customer { Name = "Customer 3" });

        Assert.AreEqual(itemCount + 1, customerList.Items.Count);
    }

    [TestMethod]
    public void CanGetCustomerByName()
    {
        if (context.IsolationLevel == IsolationLevel.Fake)
        {
            var item2 = this.customerList.Items.Add();
            item2["Title"] = "Customer 1";
            item2.Update();

            new ShimSPList(this.customerList)
            {
                GetItemsSPQuery = (q) =>
                {
                    var shim = new ShimSPListItemCollection();
                    shim.Bind(new[] { item2 });
                    return shim.Instance;
                }
            };
        }

        var result = repository.GetByName("Customer 1");
        Assert.IsNotNull(result);
    }

    [TestMethod]
    public void GetCustomerByNameReturnsNull()
    {
        if (context.IsolationLevel == IsolationLevel.Fake)
        {
            var item = this.customerList.Items.Add();
            item["Title"] = "Customer 1";
            item.Update();

            new ShimSPList(customerList)
            {
                GetItemsSPQuery = (q) =>
                {
                    var shim = new ShimSPListItemCollection();
                    shim.Bind(new SPListItem[0]);
                    return shim.Instance;
                }
            };
        }

        var result = repository.GetByName("Customer XXX");
        Assert.IsNull(result);
    }

    [TestMethod]
    public void CanGetCustomerById()
    {
        if (context.IsolationLevel == IsolationLevel.Fake)
        {
            var item = this.customerList.Items.Add();
            item["Title"] = "Customer 1";
            item.Update();
        }

        var result = repository.GetById(1);
        Assert.IsNotNull(result);
        Assert.AreEqual("Customer 1", result.Name);
    }

    [TestMethod]
    public void GetCustomerByIdReturnsNull()
    {
        var repository = new CustomerListRepository();
        var result = repository.GetById(10);
        Assert.IsNull(result);
    }

    [TestMethod]
    public void CanGetListOfCustomers()
    {
        if (context.IsolationLevel == IsolationLevel.Fake)
        {
            var item = this.customerList.Items.Add();
            item["Title"] = "Customer 1";
            item.Update();

            var item2 = this.customerList.Items.Add();
            item2["Title"] = "Customer 1";
            item2.Update();

            new ShimSPList(customerList)
            {
                GetItemsSPQuery = (q) =>
                {
                    var shim = new ShimSPListItemCollection();
                    shim.Bind(new[] { item, item2 });
                    return shim.Instance;
                }
            };
        }

        var result = repository.GetAll();
        Assert.AreEqual(2, result.Count());
    }

    [TestCleanup]
    public void TestCleanUp()
    {
        if (this.context.IsolationLevel != IsolationLevel.Fake)
        {
            var startId = 3; // if you have items deployed by default set start id to appropriate value
            var itemsToClear = from i in customerList.Items.Cast<SPListItem>()
                                where i.ID >= startId
                                select i.ID;

            foreach (var id in itemsToClear)
                customerList.Items.DeleteItemById(id);
        }

        context.Dispose();
    }
}

Conclusion

JavaScript and the new app model become more and more important for SharePoint development. But the farm solutions won’t disappear completely for a while. The #SPEmulators will help to create robust farm solutions with a high quality and test coverage for #SP2013.

NuGet: https://www.nuget.org/packages/SPEmulators/

Repository: https://github.com/wulfland/SPEmulators

9 comments

  1. My coworker and I are trying this out for Web Part Unit Testing, but there seems to be some flaw in the setup. We are both getting the error:
    “Type or namespace name “SPEmulators” could not be found.”

    Intellisense is showing its there and the classes are being recognized. But nothing we’ve been able to do has fixed it.

      1. That would certainly be helpful. Or state the testing must be done in .Net Framework 4.5.1

      2. I tried to change the framework to 4.5. But it seems all my dev machines on azure are on 4.5.1 and I get a build error, if I try to change the framework version of the project. I will spend some time on it this weekend.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s