Export (0) Print
Expand All

Unit Testing

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

The Training Management application includes unit testing withmock objects. Mock objects are instances of test-provided classes. The test run-time framework substitutes mock objects for components other than the one being tested. The goal of using mock objects is to test the application code in an isolated environment. For an introduction to unit testing with mock objects, see Mock Object on Wikipedia.

Support for unit testing best practices was one of the main motivations for the Training Management application's system architecture. (To learn about the application's architecture, see The Application Structure.) 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 pass them into the class.

Mock objects are implemented in the following three ways in the Training Management application's unit testing approach:

  • Mock views. The MVP pattern that is used by the Training Management application 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 MVP, see Design Patterns.
  • Mock services. The components that comprise the data layer of the Training Management application 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 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 Design Patterns.
  • Tool-generated mock objects for system services. Mock views and mock services are insufficient for testing the lowest layers of the application because the components that are provided by SharePoint are sealed classes with internal constructors that do not use the MVP or Service Locator patterns. In this situation, it is necessary to impersonate SharePoint classes within a special execution environment.
Ff650225.note(en-us,PandP.10).gifNote:
There are a variety of tools available that support unit testing with mock objects. The Training Management application uses 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 application's unit tests overcome many of the challenges that are inherent in testing SharePoint applications. Some of these challenges are the following:

  • Normally, 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 have been written. Only the interfaces need to be defined. This allows parallel development by multiple programmers.

The following sections give examples of the Training Management application's unit testing approach.

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 that 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();
        }
    } 
}

This 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();
}

This 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.

Testing an Event Receiver Using Mock SharePoint Objects

This section gives 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);

           ....
}

This 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.

Ff650225.note(en-us,PandP.10).gifNote:
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 our mock so that our 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 our event receiver with our 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 our mock so that our course code is invalid.
    SPItemEventProperties spItemEventProperties =
                            this.CreateMockSpItemEventProperties("My Title",
                                "1234",
                                     DateTime.Today,
                                         DateTime.Today.AddDays(1),
                                             DateTime.Today.AddDays(2),
                                                 100);

    // Call our event receiver with our 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.

private SPItemEventProperties CreateMockSpItemEventProperties(string title, string
  code, DateTime enrollmentDate, DateTime startDate, DateTime endDate, int
    courseCount, float courseCost)
{
    // Create any mock objects we will need 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.

Ff650225.note(en-us,PandP.10).gifNote:
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 our expectations for our 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.


Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Show:
© 2014 Microsoft