Sunday, February 13, 2011

CRM 2011 plug-in tips and tricks (part 2) - Testing plug-ins

Testing CRM plug-ins is now much easier than it was in previous version of CRM.   I will often create a simple console application that executes the plug-in I'm developing, just so I can make sure the core functionality of the plug-in works before I ever have to deploy it to the CRM server.  The same techniques can also be used for writing unit tests for your plug-ins.  

To create a simple test harness for your plug-ins you just need to create a few classes that implement the IServiceProvider, IOranizationServiceFactory, and IPluginExecutionContext interfaces. 


First is the TestServiceProvider Class.  This class is responsible for providing the plugin execution context and service factory to the plug-in.

public class TestServiceProvider : IServiceProvider
{
    private TestPluginContext _pluginContext;
    public TestServiceProvider(TestPluginContext pluginContext)
    {
        _pluginContext = pluginContext;
    }

    public object GetService(Type serviceType)
    {
        if (serviceType == typeof(IPluginExecutionContext))
        {
            return _pluginContext;
        }
        if (serviceType == typeof(IOrganizationServiceFactory))
        {
            return new TestServiceFactory();
        }
        return null;
    }
}


Next is the TestServiceFactory class.  This class is responsible for generating the service proxy that the plug-in will be using.  For this example I am just returning a new OrganizationServiceProxy (from the referenced Microsoft.Xrm.Sdk.dll assembly), and I have hard-coded connection info. This means that when the plug-in runs, even in the test, it will still be hitting the CRM server. You could also easily return a "fake" organizationservice that didn't actually connect to the CRM server, but just verified that the plug-in was making the correct service calls. 
public class TestServiceFactory : IOrganizationServiceFactory
{
    public IOrganizationService CreateOrganizationService(Guid? userId)
    {
        Uri serviceUrl = new System.Uri("http://crmserver/org/xrmservices/2011/organization.svc");
        ClientCredentials creds = new ClientCredentials();
        creds.Windows.ClientCredential = new System.Net.NetworkCredential("username", "pass", "domain");

        OrganizationServiceProxy _serviceProxy = new Microsoft.Xrm.Sdk.Client.OrganizationServiceProxy(serviceUrl, null, creds, null);
        _serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());
        return _serviceProxy;
    }
}

Finally, you have the TestPluginContext class.  This is our fake plugin context that we will fill with the desired test information before executing the plug-in.  Note that I am making a WhoAmI request in the constructor to set the InitiatingUserId and OrganizationId properties.  This is mostly because I was testing several plug-ins that needed to use those two fields. 
public class TestPluginContext : IPluginExecutionContext
{
    public TestPluginContext()
    {
        this.InputParameters = new ParameterCollection();
        this.OutputParameters = new ParameterCollection();

        //set the userid and the orgid on the context
        IOrganizationService service = new TestServiceFactory().CreateOrganizationService(null);
        WhoAmIResponse whoAmI = (WhoAmIResponse)service.Execute(new WhoAmIRequest());
        this.InitiatingUserId = whoAmI.UserId;
        this.OrganizationId = whoAmI.OrganizationId;

    }

    public IPluginExecutionContext ParentContext { get; set; }
    public int Stage { get; set; }
    public Guid BusinessUnitId { get; set; }
    public Guid CorrelationId { get; set; }
    public int Depth { get; set; }
    public Guid InitiatingUserId { get; set; }
    public ParameterCollection InputParameters { get; set; }
    public bool IsExecutingOffline { get; set; }
    public bool IsInTransaction { get; set; }
    public bool IsOfflinePlayback { get; set; }
    public int IsolationMode { get; set; }
    public string MessageName { get; set; }
    public int Mode { get; set; }
    public DateTime OperationCreatedOn { get; set; }
    public Guid OperationId { get; set; }
    public Guid OrganizationId { get; set; }
    public string OrganizationName { get; set; }
    public ParameterCollection OutputParameters { get; set; }
    public EntityReference OwningExtension { get; set; }
    public EntityImageCollection PostEntityImages { get; set; }
    public EntityImageCollection PreEntityImages { get; set; }
    public Guid PrimaryEntityId { get; set; }
    public string PrimaryEntityName { get; set; }
    public Guid? RequestId { get; set; }
    public string SecondaryEntityName { get; set; }
    public ParameterCollection SharedVariables { get; set; }
    public Guid UserId { get; set; }
}

Now that we have the necessary classes set up, we can create a test.  To execute the plugin, you just create a new TestPluginContext and fill it with the data needed to test your plug-in.  Then create a new TestServiceProvider from the TestPluginContext, and then pass the service provider to the Execute method of your plug-in.

TestPluginContext pluginContext = new TestPluginContext();
pluginContext.InputParameters["Target"] = user;
pluginContext.PrimaryEntityName = "systemuser";
pluginContext.MessageName = "Update";

TestServiceProvider provider = new TestServiceProvider(pluginContext);

MyPlugin plugin = new MyPlugin();
plugin.Execute(provider);

Depending on the complexity of your plug-in, you may need to provide more information in the plug-in context.  In this example, I am testing a plug-in that fires whenever a user changes business units, and the plug-in requires a pre-image of the systemUser record:

Guid buid = new Guid("EF0C0FD0-B50B-E011-8E3F-001DD8B71C42"); //old businessunit
Guid newBuid = new Guid("14880f34-711e-e011-b23e-001dd8b71c42"); //new businessunit

Entity user = new Entity("systemuser");
user["businessunitid"] = new EntityReference("businessunit", newBuid);
user["internalemailaddress"] = "erik.pool@example.com";
user["fullname"] = "Erik Pool";
user["systemuserid"] = new Guid("4C72C28A-5A0C-E011-892F-001DD8B71C42");

Entity preImage = new Entity("systemuser");
preImage["businessunitid"] = new EntityReference("businessunit", buid);
preImage["internalemailaddress"] = "erik.pool@example.com";
preImage["fullname"] = "Erik Pool";

TestPluginContext pluginContext = new TestPluginContext();
pluginContext.InputParameters["Target"] = user;
pluginContext.PreEntityImages = new EntityImageCollection();
pluginContext.PreEntityImages["Target"] = preImage;
pluginContext.PrimaryEntityName = "systemuser";
pluginContext.MessageName = "Update";

TestServiceProvider provider = new TestServiceProvider(pluginContext);
SystemUserPostUpdatePlugin plugin = new SystemUserPostUpdatePlugin();
plugin.Execute(provider);