Unit Testing SharePoint 2007 Applications
When developing SharePoint applications, it is common to develop custom:
- ASP.NET Web Forms
- Event Receivers
- Workflow coding activities
One of the first things we noticed when trying to unit test our business logic is that our code will heavily relied on elements of the SharePoint API.
A common approach to making code more testable is to identify and isolate services that a class needs to do its work, treat them as external dependencies, and passed them into the class. If these dependencies are passed in instead of being internally constructed, we have the opportunity to pass in a substitute.
By controlling the behavior of the substitute we can force specific branches of our code to be executed. This process of creating controlled substitutes for dependent services is referred to as mocking (http://en.wikipedia.org/wiki/Mock_Object).
It is often very advantageous to use a mock instead of the real service. The classic example is where our business logic handles a “Database Full” exception thrown by a dependent service. We obviously wouldn’t want to simulate that by filling up the service’s database. Rather, we would like to have our mock service simply throw the exception on demand. Unit testing our business logic is basically validating how our code interacts with dependent services. By using mocks in place of the real implementations, we can effectively simulate any condition and examine our code's response to those conditions.
So what’s the problem? Why is it so hard to unit test SharePoint applications?
Let’s treat elements of the SharePoint API such as SPWeb and SPList as dependencies that we want to mock. When we try creating a mock of any of these elements we quickly run into a few challenges.
- Interfaces are rarely used: If our code that depends on an SPWeb could instead depend on an ISPWeb interface (assuming that SPWeb implemented ISPWeb), we could easily create a mock implementation of ISPWeb. We could pass our mock into our code instead of an SPWeb. We could define the exact behavior of the mock. However, since SharePoint elements such as SPWeb, SPList, and SPItemEventProperties don’t implement public interfaces, this is not an option.
- Sealed classes: The SharePoint elements mentioned above are also sealed. Another mocking technique is to extend the type you want to mock overriding all the base class’s methods. Instances of the mock type can be substituted for the instances of the base type. However, since many SharePoint elements including the ones mentioned above are sealed, this technique is also not an option.
- Internal Constructor: Even if there were no behavior in our dependent service that we need to override, we still would need to create an instance of it. Again, many SharePoint elements including the ones mentioned above have internal constructors so we are not expected to new up our instances, but rather get them for calling the SharePoint API.
- Collections with no Add method: Many of the collections provided by SharePoint such as SPItemEventDataCollection do not have a public Add method. This makes it difficult to mock if we can’t populate an instance of this collection with a controlled set of values.
Bottom Line… the SharePoint API is Hard To Mock!!!
Unit Testing Options
- Status Quo: Hard dependency on a SharePoint instance. Many people who try to unit test their SharePoint applications simply accept the SharePoint dependencies and run their unit tests against a live instance of SharePoint.
- One challenge with this approach is that these “unit” tests are really more like integration tests in that the success of the test depends upon many systems functioning and configured correctly. For example, if there were a problem with the SharePoint database, your unit tests would likely fail, even if the tests are not related to persistence.
- Another challenge with this approach is performance. Unit tests can provide the developer with timely feedback. If the unit tests have to run against a live instance of SharePoint, they likely will take considerably longer to run and development may be slowed as a result. (Best case scenario is to have the entire suite of unit tests run after every check-in, and to not make another check-in until failing tests have been resolved. This makes it clear what modification introduced a problem.)
- If unit tests are run against a live instance of SharePoint, there is considerable work that needs to be done to reset the environment to a known state so that further test runs will perform as expected. If this work fails, tests will fail unexpectedly.
- Wrapper/Façade: Another option is to provide an abstraction to the SharePoint API that can be mocked. A wrapper is an abstraction that provides a mirror of the SharePoint API. A façade is an abstraction that simplifies the SharePoint API. Either approach takes a lot of work and may be very difficult to do well.
- Typemock Isolator: Typemock Isolator provides the ability to create a mock instance of type, even if it is sealed and internally constructed.
We have successfully built unit tests leveraging Typemock Isolator with over 80% code coverage of our TrainingManagement reference implementation business logic.
Strategies on Unit Testing SharePoint Applications
- Model-View-Presenter to test web forms
The MVC and MVP patterns we created to help separate business logic from view logic. The main reason for separating the business logic from the view logic is to improve testability.
- Use repositories to encapsulate access to SPList instances
We created abstractions to the Training Course and Registration SharePoint lists following the Repository pattern. We then refactored our business logic to use the TrainingCourseRepository and RegistrationRepository instead of going through the SharePoint API to perform create, read, update & delete operations. Our unit tests simply use mock versions of the repositories thus reducing the need mock the SharePoint API.
- Use Typemock to mock calls to SharePoint API
Even though the repositories reduced the need to mock the SharePoint API, there are many SharePoint elements besides SPList that need to be mocked. Typemock Isolator is used for mocking dependencies that are difficult to mock.