Unit Testing Dynamics CRM 2011 Pipeline Plugins using Rhino Mocks

It’s been a while since Dynamics CRM 2011 Beta 1 was released (surely we are due Beta 2 soon!) so I thought it was about time I set up a Unit Test framework for PlugIns. I’ve been using Rhino Mocks for a while to great effect, so here we go!

This example aims to unit test the SDK sample Plugins, and demonstrates the following:

    • Mocking the pipeline context, target and output property bags.
    • Mocking the Organisation Service.
    • How to assert that exceptions are raised by a plugin
    • How to assert that the correct Organisation Service method was called with the desired values.

To build the examples, you’ll need the CRM2011 SDK example plugins and Rhino Mocks 3.6 (http://ayende.com/Blog/archive/2009/09/01/rhino-mocks-3.6.aspx). 

The key principle of mocking is that we can exercise and examine the code that we need to test without executing the bits that are not being tested. By mocking we are fixing behaviour and return values of the dependant code so that we can assert if the results are what we expect.  This approach supports Test Driven Development (TDD), where the test is written first and then the desired functionality is added in order that the test passes. We can then say we are ‘Done’ when all tests pass. 

So in our example, the Followup Plugin should create a task with the regarding id set to the id of the account. So by mocking the pipeline context, we can specify the account id, and check that the resulting task that is created is regarding the same account. Using Rhino Mocks allows us to create a mock Organisation Service and assert that the Create method was called passing a task with the desired attributes set.

 

[TestMethod]
public void FollowupPlugin_CheckFollowupCreated()
{
    RhinoMocks.Logger = new TextWriterExpectationLogger(Console.Out);

    // Setup Pipeline Execution Context Stub
    var serviceProvider = MockRepository.GenerateStub();
    var pipelineContext = MockRepository.GenerateStub();
    Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext)
        serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));
    serviceProvider.Stub(x => x.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext))).Return(pipelineContext);

    // Add the target entity
    ParameterCollection inputParameters = new ParameterCollection();
    inputParameters.Add("Target", new Entity("account"));
    pipelineContext.Stub(x => x.InputParameters).Return(inputParameters);

    // Add the output parameters
    ParameterCollection outputParameters = new ParameterCollection();
    Guid entityId= Guid.NewGuid();

    outputParameters.Add("id", entityId);
    pipelineContext.Stub(x => x.OutputParameters).Return(outputParameters);

    // Create mock OrganisationService
    var organizationServiceFactory = MockRepository.GenerateStub();
    serviceProvider.Stub(x => x.GetService(typeof(Microsoft.Xrm.Sdk.IOrganizationServiceFactory))).Return(organizationServiceFactory);
    var organizationService = MockRepository.GenerateMock();
    organizationServiceFactory.Stub(x => x.CreateOrganizationService(Guid.Empty)).Return(organizationService);


    // Execute Plugin
    FollowupPlugin plugin = new FollowupPlugin();
    plugin.Execute(serviceProvider);

    // Assert the task was created
    organizationService.AssertWasCalled(x => x.Create(Arg.Is.NotNull));

    organizationService.AssertWasCalled(x => x.Create(Arg.Matches(s => 
            ((EntityReference)s.Attributes["regardingobjectid"]).Id.ToString() == entityId.ToString()
            &&
            s.Attributes["subject"].ToString() == "Send e-mail to the new customer."
            )));

}

The key thing to notice is that the only mock object here is the OrganisationService - all others are stubs. The difference between a stub and a mock is that the mock records the calls that are made so that they can be verified after the test has been run. In this case we are verifying that the Create method was called with the correct properties set on the task entity.

It’s worth noting the RhinoMocks.Logger assignment. This gives the Rhino logging output in the VS2010 test results; most helpful during debugging asserts that don’t do as you expect.

Looking at the sample Account Plugin, it throws an exception when the account number is already set - so what testing that plugin’s throw exceptions under some conditions. Unfortunately, the standard VS2011 ExpectedExceptionAttribute doesn’t provide the functionality we need here since we can’t check exception attributes, nor can we run the tests in Debug without the Debugger breaking into the code even though the exception is marked as being expected. In order to get around this this I use a class written by Keith E. Burnell:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Kb.Research.RhinoMocks.UnitTests.CusomAssertions
{
    /// 
    /// Custom assertion class for unit testing expected exceptions.  
    /// A replacement for the ExpectedException attribute in MSTest
    /// 
    public static class AssertException
    {
        #region Methods

        /// 
        /// Validates that the supplied delegate throws an exception of the supplied type
        /// 
        /// Type of exception that is expected to be thrown
        /// Delegate that is expected to throw an exception of type 
        public static void Throws<TExpectedExceptionType>(Action actionThatThrows) where TExpectedExceptionType : Exception
        {
            try
            {
                actionThatThrows();
            }
            catch (Exception ex)
            {
                Assert.IsInstanceOfType(ex, typeof(TExpectedExceptionType), String.Format("Expected exception of type {0} but exception of type {1} was thrown.", typeof(TExpectedExceptionType), ex.GetType()));
                return;
            }
            Assert.Fail(String.Format("Expected exception of type {0} but no exception was thrown.", typeof(TExpectedExceptionType)));
        }

        /// 
        /// Validates that the supplied delegate throws an exception of the supplied type
        /// 
        /// Type of exception that is expected to be thrown
        /// Expected message that will be included in the thrown exception
        /// Delegate that is expected to throw an exception of type 
        public static void Throws<TExpectedExceptionType>(string expectedMessage, Action actionThatThrows) where TExpectedExceptionType : Exception
        {
            try
            {
                actionThatThrows();
            }
            catch (Exception ex)
            {
                Assert.IsInstanceOfType(ex, typeof(TExpectedExceptionType), String.Format("Expected exception of type {0} but exception of type {1} was thrown.", typeof(TExpectedExceptionType), ex.GetType()));
                Assert.AreEqual(expectedMessage, ex.Message, String.Format("Expected exception with message '{0}' but exception with message '{1}' was thrown.", ex.Message, expectedMessage));
                return;
            }
            Assert.Fail(String.Format("Expected exception of type {0} but no exception was thrown.", typeof(TExpectedExceptionType)));
        }

        #endregion

    }
}

So, we want to test that if the account number is already set on execution of the AccountNumberPlugin, then an exception is raised:

[TestMethod]
public void AccountNumberPlugin_CheckExceptionIfAccountNumberSetAllready()
{
    // Setup Pipeline Execution Context Stub
    var serviceProvider = MockRepository.GenerateStub();
    var pipelineContext = MockRepository.GenerateStub();
    Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext)
        serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));
    serviceProvider.Stub(x => x.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext))).Return(pipelineContext);

    // Add the target entity
    ParameterCollection inputParameters = new ParameterCollection();
    inputParameters.Add("Target",new Entity("account") { Attributes = new AttributeCollection  { 
            new KeyValuePair("accountnumber", "123")
        }});

    pipelineContext.Stub(x => x.InputParameters).Return(inputParameters);

    // Test that an exception is thrown if the account number already exists
    AccountNumberPlugin plugin = new AccountNumberPlugin();
    AssertException.Throws < InvalidPluginExecutionException> ("The account number can only be set by the system.",(Action) delegate {
            plugin.Execute(serviceProvider);
            });

}

The examples above show how to test plugins that don’t call any external services or code - where all dependencies are discovered via the execution context. Next time I’ll provide an example of how to mock an external service using inversion of control.

 

 

Pingbacks and trackbacks (5)+

Comments are closed