Unit Testing with Mock Objects

The Training Management and Partner Portal applications include unit tests that use mock objects. Mock objects are instances of test-provided classes that simulate the behavior of external components. Mock objects isolate the application code under test. They create conditions that are otherwise difficult to produce, such as a disk full exception. For more information about mock objects, see Exploring The Continuum Of Test Doubles in MSDN Magazine.

Mock objects are implemented in the following three ways in the Training Management and Partner Portal applications' unit tests:

  • Mock views. The Model-View-Presenter (MVP) pattern that is used by the Training Management and Partner Portal applications contains presenter classes that encapsulate business logic. Presenter classes update the properties of view interfaces that are provided by the top layer of the application. Unit tests exercise the functionality of a presenter class in isolation by providing a mock view implementation when running the unit test. For more information about the MVP pattern, see The Model-View-Presenter (MVP) Pattern.
  • Mock services. The components that comprise the data layer of the Training Management and Partner Portal applications are exposed using the Service Locator pattern. This pattern puts the selection of the interface implementation under programmer control at run time. The Training Management and Partner Portal unit tests take advantage of this architecture when testing the presenter layer by substituting test-specific stub implementations that provide the inputs and outputs that are needed for the specific test scenario. For more information about the Service Locator pattern, see The Service Locator Pattern and The SharePoint Service Locator.
  • Tool-generated mock objects for system services. Mock views and mock services are insufficient for testing the lowest layers of the application. For example, components that are provided by SharePoint are sealed classes with internal constructors that do not provide interfaces that allow for substitution. In this situation, it is necessary to impersonate SharePoint classes in a special execution environment.

Note

There are a variety of tools available that support unit testing with mock objects. The Training Management and Partner Portal applications use a commercially available testing tool named Typemock Isolator that is provided by Typemock. You must install this tool if you want to run the unit tests.

By using mock objects to isolate the code to be tested, the Training Management and Partner Portal applications' unit tests overcome many of the challenges that are inherent in testing SharePoint applications. The following are some of these challenges:

  • Usually, application code that invokes SharePoint-provided classes can only execute on the SharePoint server. This means that the code must be deployed before it can be unit tested with any testing framework and that remote debugging must be used. By simulating SharePoint objects and by using the MVP and Service Locator patterns that permit run-time—swappable components, the unit tests can be executed in isolation on a local development computer.
  • It is difficult to test error conditions and exceptions with live, system-level tests. By replacing system components with mock objects, it is possible to simulate error conditions within the unit test. An example is when the business logic handles a Database Full exception that is thrown by a dependent service. Obviously, you do not actually fill up the service's database. Instead, the mock service throws the exception when it is appropriate for the unit test.
  • The complexity of configuring the testing environment is greatly reduced. It is undesirable for a unit test to depend on many subcomponents. For example, without mock objects, a unit test would fail if there were a problem with the SharePoint database, even though the test was not related to persistence.
  • One problem with running unit tests against an actual instance of SharePoint is that it is difficult to configure the application that is running on SharePoint so that it is in a known state. This is required so that the unit tests perform as expected. A benefit of using mock objects is that you can always control the state.
  • Mock objects improve the performance of the unit tests themselves. This is important because a best practice is to run all unit tests before each check-in. If the unit tests run slowly, this may become difficult or impossible.
  • It is possible to write and run unit tests before all the application components are written. Only the interfaces need to be defined. This allows parallel development by multiple programmers.

The following sections include examples of the unit testing approach used by the Training Management and Partner Portal applications.

Testing the Course Registration Presenter with a Mock View and Mock Service

The CourseRegistrationPresenter class calculates the values that are used by the Training Management application's Web interface when an employee attempts to register for a training course. For more information, see Register for a Course Use Case.

The Mock View

Unit tests for the CourseRegistrationPresenter class provide a mock view as an argument to the CourseRegistrationPresenter constructor. The implementation of the mock view is shown in the following code. This code is located in the CourseRegistrationPresenterFixture.cs file of the Contoso.TrainingManagement.Tests project in Contoso.RI (VSTS Tests).sln.

private class MockCourseRegistrationView : ICourseRegistrationView
{
    public string PageTitle { get; set; }

    public string HeaderTitle { get; set; }

    public string HeaderSubTitle { get; set; }

    public string ContentMessage { get; set; }

    public bool ShowConfirmationControls { get; set; }

    public IList<TrainingCourse> Courses { get; set; }
    public bool ShowCourseSelectionControls { get; set; }
    public System.Collections.Specialized.NameValueCollection QueryString 
        { get; set; }

    public string SelectedCourse { get; set; }
    public string SiteLink { get; set; }
}

The preceding code illustrates that the MockCourseRegistrationView class, which is used by the unit test, is a stub implementation that provides the properties of the interface.

The Mock Service

Unit tests for the CourseRegistrationPresenter class replace the application's Repository components with mock services. The following code is for a mock Registration Repository service. The code is located in the MockRegistrationRepository.cs file of the Contoso.TrainingManagement.Mocks project.

public class MockRegistrationRepository : IRegistrationRepository
{

    public static Registration RegistrationReturnedByGet { get; set; }
    public static Registration UpdateCalledWithRegistrationParam { get; set; }

    public static void Clear()
    {
        RegistrationReturnedByGet = null;
        UpdateCalledWithRegistrationParam = null;
    }

    public int Add(Registration registration, SPWeb spWeb)
    {  
        RegistrationReturnedByGet = registration;
        return 1;
    }

    public void Delete(int id, SPWeb spWeb)
    {
        throw new System.NotImplementedException();
    }

    public Registration Get(int id, SPWeb spWeb)
    {
        return RegistrationReturnedByGet;
    }

    public Registration Get(int courseId, int userId, SPWeb spWeb)
    {
        return RegistrationReturnedByGet;
    }

    public void Update(Registration registration, 
SPWeb spWeb)
    {
        UpdateCalledWithRegistrationParam = registration;
    }

    public string GetFieldName(Guid key, SPWeb spWeb)
    {
        switch ( key.ToString().ToUpper() )
        {
            case "FA564E0F-0C70-4AB9-B863-0177E6DDD247":
                return "Title";
            case "E5509750-CB71-4DE3-873D-171BA6448FA5":
                return "TrainingCourseCode";
            case "7E4004FA-D0BE-4611-A817-65D17CF11A6A":
                return "TrainingCourseCost";
            case "8E39DAD4-65FA-4395-BA0C-43BF52586B3E":
                return "TrainingCourseDescription";
            case "43568365-8448-4130-831C-98C074B61E89":
                return "TrainingCourseEnrollmentDate";
            case "AE2A0BBD-F22E-41DC-8753-451067122318":
                return "TrainingCourseStartDate";
            case "F5E6F566-FA7C-4883-BF7F-006727760E22":
                return "TrainingCourseEndDate";
            default:
                throw new NotImplementedException();
        }
    } 
}

The preceding code demonstrates how to create a mock registration repository service that impersonates the real registration repository during the unit tests. Note that several of the methods in the mock classes throw Not Implemented exceptions because they are not required for testing. You must implement the interface but not all of its methods. Implement only the methods you need to create an effective unit test.

Using the Mock View and Mock Service in a Unit Test

The following unit test for the CourseRegistrationPresenter class uses the MockCourseRegistrationView and MockRegistrationRepository classes. This code is located in the CourseRegistrationPresenterFixture.cs file of the Contoso.TrainingManagement.Tests project.

[TestMethod]
public void RenderCourseRegistrationPopulatesView()
{
    string loginName = @"domain\alias";
    string courseId = "1";
    SPWeb mockWeb = CreateMockSPWeb(false);

    MockCourseRegistrationView mockView = new MockCourseRegistrationView();
    mockView.QueryString = new System.Collections.Specialized.NameValueCollection();
    mockView.QueryString["ID"] = courseId;

    TrainingCourse course = new TrainingCourse() { Id = 1, Code = "TestCode" };
    MockTrainingCourseRepository.TrainingCourseReturnedByGet = course;

    this.serviceLocator.Clear();
    this.serviceLocator.Register<IRegistrationRepository>( typeof(MockRegistrationRepository));
    this.serviceLocator.Register<ITrainingCourseRepository>( typeof(MockTrainingCourseRepository));

    CourseRegistrationPresenter presenter = 
                                           new CourseRegistrationPresenter(mockView);
    presenter.RenderCourseRegistrationView(web, loginName);

    Assert.AreEqual<string>("Course Registration - TestCode", mockView.PageTitle);
    Assert.AreEqual<string>("Course Registration", mockView.HeaderTitle);
    Assert.AreEqual<string>("TestCode", mockView.HeaderSubTitle);
    Assert.AreEqual<string>("Would you like to register for course: TestCode?",
                                                            mockView.ContentMessage);
    Assert.IsTrue(mockView.ShowConfirmationControls);
    Assert.IsFalse(mockView.ShowCourseSelectionControls);
    Assert.AreEqual("http://localhost/training", mockView.SiteLink);
    MockManager.Verify();
}

The preceding code performs the following actions:

  • It creates an instance of the mock view.
  • It configures the mock training course repository with specific data values (a training course with Id=1 and Code="TestCode") that are returned by the Get operation.
  • It configures the service locator object to use the mock TrainingCourse and registration repository services instead of the real implementations.
  • It instantiates a new CourseRegistrationPresenter object and passes the mock view as the argument to the constructor.
  • It invokes the method to be tested, which is RenderCourseRegistrationView.
  • It performs checks to see that the output values, as stored in the mock view, are the expected values.
  • It invokes the Verify method of the MockManager class or object. This call validates that all the Typemock expectations have been met. For more information about using Typemock, see Creating Mock Objects with the Typemock Isolator later in this topic.

Testing an Event Receiver Using Mock SharePoint Objects

This section includes an example of how to use the Typemock Isolator tool to generate mock SharePoint objects when testing the TrainingCourseItemEventReceiver class's ItemAdding event receiver method.

The Method Under Test

The following code shows the ItemAdding method. This code is found in TrainingCourseItemEventReceiver.cs file of the Contoso.TrainingManagement project.

public override void ItemAdding(SPItemEventProperties properties)
{
    bool isValid = true;
    StringBuilder errorMessage = new StringBuilder();

    string title = string.Empty;
    string code = string.Empty;
    DateTime enrollmentDate = DateTime.MinValue;
    DateTime startDate = DateTime.MinValue;
    DateTime endDate = DateTime.MinValue;
    float cost = 0;

    using (SPWeb web = properties.OpenWeb())
    {
        ITrainingCourseRepository repository =
                       ServiceLocator.GetInstance().Get<ITrainingCourseRepository>();
        this.Initalize(properties.AfterProperties, repository, web, out title, 
                 out code, out enrollmentDate, out startDate, out endDate, out cost);

           ....
}

The preceding code takes an instance of the SharePoint SPItemEventProperties class as its input. It then queries this instance for the following values: title, code, enrollmentDate, StartDate, and cost. It also retrieves a SharePoint SPWeb object by invoking the OpenWeb method.

Note

The standard .NET Framework run-time environment is insufficient for simulating inputs of this kind because the SharePoint SPItemEventProperties class is sealed. It would be impossible in this case to impersonate this class by creating a derived class (a wrapper or façade) that overrides all of the relevant methods.

The Unit Test

The following code shows the unit test for the ItemAdding method. This code is found in the TrainingCourseItemEventReceiverFixture.cs file in the Contoso.TrainingManagement.Tests project.

[TestMethod]
public void ItemAddingPositiveTest()
{
    serviceLocator.Clear();
    serviceLocator.Register<ITrainingCourseRepository>(typeof(MockTrainingCourseRepository));

    // Set up the mock so the validations pass.
    SPItemEventProperties spItemEventProperties =
                           this.CreateMockSpItemEventProperties("My Title",
                               12345678",
                                   DateTime.Today,
                                       DateTime.Today.AddDays(1),
                                           DateTime.Today.AddDays(2),
                                               0, 100);
    ....
}

First, the unit test clears the ServiceLocator of all type mappings and then adds a mapping to a mock training course repository. The unit test then makes a call to a helper method named CreateMockSpItemEventProperties to create a mock object with the appropriate values.

The ItemAdding method is tested after the inputs are established. This is shown in the following code.

{
    ....
    // Call the event receiver with the mocked SPItemEventProperties.
    TrainingCourseItemEventReceiver receiver = new TrainingCourseItemEventReceiver();
    receiver.ItemAdding(spItemEventProperties);

    // Assert that the cancel did not get set.
    Assert.IsFalse(spItemEventProperties.Cancel);
}

When the ItemAdding method is called, all calls to objects that are referred to by the SPItemEventProperties object are intercepted by the Typemock Isolator run-time environment. The environment causes the test-specific values to be returned from the calls to the mock SPItemEventProperties that is given as the argument to the ItemAdding method.

After the method under test, which uses the data that is provided by the mock objects, completes, the unit test calls Assert.IsFalse to check that the update succeeded. (The SPItemEventProperties.Cancel property is set to true to cancel the add operation.)

Both positive and negative tests can be created using mock objects. For example, there is a unit test that uses incorrect data to check the behavior of the event receiver. The following code shows an example of a negative test.

[TestMethod]
public void AddingCourseWithInvalidCourseCodeCancelsWithError()
{
    serviceLocator.Clear();
    serviceLocator.Register<ITrainingCourseRepository>(typeof(MockTrainingCourseRepository));

    // Set up the mock so the course code is invalid.
    SPItemEventProperties spItemEventProperties =
                            this.CreateMockSpItemEventProperties("My Title",
                                "1234",
                                     DateTime.Today,
                                         DateTime.Today.AddDays(1),
                                             DateTime.Today.AddDays(2),
                                                 100);

    // Call the event receiver with the mocked SPItemEventProperties.
    TrainingCourseItemEventReceiver receiver = new TrainingCourseItemEventReceiver();
    receiver.ItemAdding(spItemEventProperties);

    StringAssert.Contains(spItemEventProperties.ErrorMessage, "The Course Code must
                                                             be 8 characters long.");
    Assert.IsTrue(spItemEventProperties.Cancel);
}

In this case, the Assert checks that the Cancel property is set because the course code is not eight characters long. This should cause the operation to fail.

Creating Mock Objects with the Typemock Isolator Tool

The helper method CreateMockSpItemEventProperties that was used in the previous examples contains the code that invokes the Typemock Isolator tool. The following code is an example of how to use the tool. For more information about the Typemock Isolator tool, see the Typemock Web site. Also, Typemock provides a video on Unit Testing SharePoint with Typemock Isolator.

Note

The following code is written using the Natural Mocks API from Typemock. Typemock 5.0 provides a C# Arrange, Act and Assert (AAA) API. The Partner Portal unit tests use the new AAA API. For an example of the AAA API, see the unit tests that are implemented in the PartnerSiteDirectoryFixture class.

private SPItemEventProperties CreateMockSpItemEventProperties(string title, string
  code, DateTime enrollmentDate, DateTime startDate, DateTime endDate, int
    courseCount, float courseCost)
{
    // Create any mock objects that will be needed here.
    SPItemEventProperties spItemEventProperties = 
                         RecorderManager.CreateMockedObject<SPItemEventProperties>();
    SPWeb spWeb = RecorderManager.CreateMockedObject<SPWeb>();
    SPList list = RecorderManager.CreateMockedObject<SPList>();
    SPItemEventDataCollection afterProperties = 
                     RecorderManager.CreateMockedObject<SPItemEventDataCollection>();
    ....
}

The RecorderManager.CreateMockedObject method is provided by the Typemock Isolator tool. It takes the type to be impersonated as a type argument and returns a mock object.

Note

This code depends on the presence of the special run-time environment provided by the Typemock Isolator tool. The objects returned are mock objects; they are not instances provided by the SharePoint system.

Next, the code tells the tool what values to return in response to various method invocations.

{
    ...
    // Record expectations for the AfterProperties collection.
    MockHelper.RecordSPItemEventDataCollection(afterProperties, "Title", title);
    MockHelper.RecordSPItemEventDataCollection(afterProperties, "TrainingCourseCode",
                                                                               code);
    MockHelper.RecordSPItemEventDataCollection(afterProperties,
                                     "TrainingCourseEnrollmentDate", enrollmentDate);
    MockHelper.RecordSPItemEventDataCollection(afterProperties,
                                               "TrainingCourseStartDate", startDate);
    MockHelper.RecordSPItemEventDataCollection(afterProperties,
                                                   "TrainingCourseEndDate", endDate);
    MockHelper.RecordSPItemEventDataCollection(afterProperties, "TrainingCourseCost",
                                                                         courseCost);

    return spItemEventProperties;
}

The preceding code invokes the RecordSPItemEventDataCollection method of a helper class named MockHelper to define the argument/return value pairs. The following code is the method. It is found in the MockHelper.cs file in the Contoso.TrainingManagement.Mocks project.

public static void RecordSPItemEventDataCollection(SPItemEventDataCollection
                                          properties, string fieldName, object value)
{
    using (RecordExpectations recorder = RecorderManager.StartRecording())
    {
        object val = properties[fieldName];
        recorder.Return(value).RepeatAlways().WhenArgumentsMatch();
    }
}

The StartRecording method of the Typemock Isolator tool establishes the pattern for future .NET Framework calls. It provides a context for establishing the call arguments and return values that are expected for this unit test. In this case, the code says that the property lookup operation will always return the specified value. The recording feature allows you to provide an expected pattern of calls and return values that will be used in your unit test.

Testing an Item Event Receiver Using Typemock Isolator AAA API

The Partner Portal application's unit tests use the Typemock Isolator AAA API. One example is the unit test for the ItemAdding method of the IncidentTaskReceiver class.

The Method Under Test

The following code shows the ItemAdding method of the IncidentTaskReceiver class. It can be found in the IncidentTaskReceiver.cs file in the Contoso.PartnerPortal.Collaboration.Incident project.

public override void ItemAdding(SPItemEventProperties properties)
{
    try
    {
        incidentManagementRepository.WriteToHistory(
            string.Format(CultureInfo.CurrentCulture, TaskCreatedMessage, 
                          properties.AfterProperties[TitleField]));
    }
    catch (Exception ex)
    {
        ListExceptionHandler handler = new ListExceptionHandler();
        handler.HandleListItemEventException(ex, properties, 
                                        Resources.HandleListItemEventExceptionValue);
    }
}

The ItemAdding method is the event handler that is invoked when an item of a specific content type is added to a list. The method attempts to write log information. It cancels the add operation if the call to WriteToHistory fails.

To call this method during a unit test, an SPItemEventProperties instance populated with test data is required. However, because SharePoint's SPItemEventProperties class is sealed and internally constructed, the unit test uses Typemock Isolator to create the required mock object.

The Unit Test

The following code is a unit test for the ItemAdding method of the IncidentTaskReceiver class.

[TestMethod]
public void ItemAddingThrowsExceptionTest()
{
    SPItemEventProperties properties = Isolate.Fake.Instance<SPItemEventProperties>(
                                                       Members.ReturnRecursiveFakes);

    SharePointServiceLocator.ReplaceCurrentServiceLocator(
              new ActivatingServiceLocator()
                     .RegisterTypeMapping<ILogger, MockLogger>()                                          
                          .RegisterTypeMapping<IIncidentManagementRepository,  
                                                MockIncidentManagementService>()
                             .RegisterTypeMapping<IPartnerSiteDirectory,  
                                                       MockPartnerSiteDirectory>());

    IncidentTaskReceiver receiver = new IncidentTaskReceiver();
    receiver.ItemAdding(properties);

    Assert.IsTrue(properties.Cancel);
    Assert.AreEqual("Could not write the incident task to history. Please contact support.", properties.ErrorMessage);
    Assert.IsTrue(MockLogger.errorMessage.Contains("Service threw exception"));
}

The unit test creates a mock object representing a SharePoint SPItemEventProperties instance that will be passed into the method under test, ItemAdding.

Note

The mock object in this test method is created using the C# Arrange, Act and Assert (AAA) API in Typemock Isolator. This API is different than the Natural Mocks API used in the unit tests for the Training Management application. The AAA API is simpler.

Next, the unit test configures the service locator with a mock logger class, mock incident management repository class, and mock partner site directory class.

The unit test calls the ItemAdding method. The test is able to check that the business logic in ItemAdding canceled the add operation and logged the appropriate error message. This behavior is expected because the mock incident management repository object used for this test was designed to throw an exception every time it is called.

The unit test includes a mock logger class that has a field that contains the message logged during the operation. The unit test uses this information to verify that the expected log message was written.

Note

The Partner Portal reference implementation uses the SharePoint service locator implementation from the SharePoint Guidance Library. The implementation of the service locator in the SharePoint Guidance Library differs from the service locator implementation found in the Training Management reference implementation.

Home page on MSDN | Community site