As a TDD aficionado I never feel my code complete without writing unit tests. However you may not always be lucky to be working with a framework which has been designed with testability in mind, for e.g. ASP.NET MVC or WF4. CRM 2011’s plug-in programming model is definitely not one that belong to the “testable” category. This weekend I decided to venture out to see what it takes to unit test a a CRM plug-in.

CRM plug-ins are designed to be run inside the CRM server’s execution environment (mostly sandboxed) and often access CRM’s web services to create or retrieve entities. Hence it was clear that we have to use test doubles to make the plug-in run without the CRM environment. I decided to go with Moles framework from MSR (Microsoft Research), which can be downloaded for free. If you are not familiar with test doubles, I strongly recommend you read Martin Fowler’s concise and precise piece on it here or take a look at the xunitpatterns.org website.

Download and Install Moles Framework

Download and install Moles framework from here. Remember to download x86 or x64 versions depending on your VS 2010 version.

CRM Plug-in

The sample CRM plug-in shown in this blog leverages CRM 2011 developer toolkit. Though I strongly recommend it, it is in no way a pre-requisite for understanding how to unit-test a plug-in. Code below shows a very simple plug-in which validates the “telephone1” attribute of the account entity and throws an exception if the phone number is not a valid US phone number.

  1. protected void ExecutePreValidateAccountCreate(LocalPluginContext localContext)
  2. {
  3.     if (localContext == null)
  4.     {
  5.         throw new ArgumentNullException("localContext");
  6.     }
  7.     var account = localContext.PluginExecutionContext.InputParameters.ContainsKey("Target") ?
  8.         localContext.PluginExecutionContext.InputParameters["Target"] as Entity : null;
  9.     if (account == null) return;
  10.     if (!account.Attributes.ContainsKey("telephone1")) return;
  11.     string phonenumber = account.Attributes["telephone1"] as string;
  12.     if (string.IsNullOrEmpty(phonenumber)) return;
  13.     ValidatePhoneNumber(phonenumber);
  14. }
  15.  
  16. private void ValidatePhoneNumber(string phoneNumber)
  17. {
  18.     Regex regexObj =    new Regex(@"^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$");
  19.  
  20.     if (!regexObj.IsMatch(phoneNumber))
  21.     {
  22.         throw new InvalidPluginExecutionException("Invalid phone number" + phoneNumber);
  23.     }
  24. }

Unit test project

Add a test project to your CRM 2011 solution. Add a reference to Microsoft.Moles.Framework assembly

image 

Add references to Microsoft.Xrm.Sdk and your plug-in assemblies to the test project.

Creating Unit test

Let us start writing the unit test for the plug-in. The code in your unit test can be as simple as creating an object of your plug-in class and calling its Execute method.

Unit test
  1. [TestClass]
  2. public class PreValidateAccountCreateTest
  3. {
  4.     [TestMethod]
  5.     public void TestPreValidateAccountCreate()
  6.     {
  7.     }
  8. }

However Execute method of the IPlugin Interface expects a parameter of type IServiceProvider from the System namespace in mscorlib assembly, a standard interface existed since .NET framework 1.0 days. IServiceProvider is nothing but a container (or dictionary) of services, indexed by the types of the contained services (or its abstract base classes/interfaces). When the plug-in is being executed by the CRM platform, CRM platform is responsible for creating the IServiceProvider object populated with services needed by the  plug-in and passing it to the Execute method. In a unit test method it is the responsibility of the author to provide all the dependencies required to run (test) the code being tested. Though you can manually create your own implementation of IServiceProvider and pass it to the Execute method, Moles framework comes handy here.

Creating Stubs

Right-click the “References” node of your Test project to “Add moles assembly for mscorlib”. This action add a new file to your project “mscorlib.moles”. Compile your test project and you will see that a reference to mscorlib.Moles assembly has been added to your project automatically.

image

mscorlib.moles assembly contains stubs for various interfaces -including IServiceProvider- from the mscorlib assembly. What this means is you can re-write your boilerplate unit test code as shown below

Unit test
  1. [TestClass]
  2. public class PreValidateAccountCreateTest
  3. {
  4.     [TestMethod]
  5.     public void TestPreValidateAccountCreate()
  6.     {
  7.         var serviceprovider = new SIServiceProvider();
  8.         var plugin = new PreValidateAccountCreate();
  9.         plugin.Execute(serviceprovider);
  10.     }
  11. }

Take a look at the SIServiceProvider class, which is present in the System.Moles namespace. The name of the class merits some decoding since it will help you understand the standard nomenclature moles framework uses. The class is named SIServiceProvider because it is a “Stub” for “IServiceProvider” interface. Any stub class moles framework creates will be named as per this rule. This stub class, more than implementing the IServiceProvider interface, allows you to substitute methods and properties you are interested in (or your code -being tested-uses).

We have used the stub class for IServiceProvider in the above code, however it is not complete without stubbing the GetService method of IServiceProvider. Below is the temporary stub for the GetService method

  1. serviceprovider.GetServiceType = delegate(Type type)
  2. {
  3.     if (type.Equals(typeof(IPluginExecutionContext)))
  4.         return null; //stub of pluginexecution context;
  5.     else if (type.Equals(typeof(IOrganizationServiceFactory)))
  6.         return null;//stub of organization service factory;
  7.     return null;
  8. };

when CRM platform executes a plug-in it expects the IServiceProvider container object to contain three objects of type ITracingService, IPluginExecutionContext and IOrganizationServiceFactory.  ITracingService is not mandatory if you are not doing any tracing, hence I have omitted the check in the GetService method stub.

As you guessed correctly, I am going to add stubs for IPluginExecutionContext and IOrganizationServiceFactory and they will be named as SIPluginExectuionContext and SIOrganizationServiceFactory. Since these interfaces are present in the Microsoft.Xrm.Sdk assembly, let us add  a mole file and create the stub classes. Right-click the Microsoft.Xrm.Sdk reference node and “Add Moles Assembly”. 

Add-reference 

This will add a new file called Microsoft.Xrm.Sdk.moles to the project. Build your test project, note that a new reference to “Microsoft.Xrm.Sdk.Moles” assembly is added automatically. This assembly contains stubs for interfaces explained earlier. The refactored unit test method using these stub classes should look like 

  1. [TestMethod]
  2. public void TestPreValidateAccountCreate()
  3. {
  4.     var serviceprovider = new SIServiceProvider();
  5.     var pluginec = new SIPluginExecutionContext();
  6.     var orgsf = new SIOrganizationServiceFactory();
  7.  
  8.     serviceprovider.GetServiceType = delegate(Type type)
  9.     {
  10.         if (type.Equals(typeof(IPluginExecutionContext)))
  11.             return pluginec;
  12.         else if (type.Equals(typeof(IOrganizationServiceFactory)))
  13.             return orgsf;
  14.         return null;
  15.     };
  16.  
  17.     var plugin = new PreValidateAccountCreate();
  18.     plugin.Execute(serviceprovider);
  19. }

We have successfully stubbed the GetService method of the IServiceProvider interface. Similarly we have to stub out methods and properties of IPluginExecutionContext and IOrganizationServiceFactory.

Following code shows how we can stub IPluginExecutionContext interface.

  1. var pluginec = new SIPluginExecutionContext();
  2. pluginec.UserIdGet = delegate() { return new Guid(); };
  3. pluginec.StageGet = delegate() { return 10; };
  4. pluginec.MessageNameGet = delegate() { return "Create"; };
  5. pluginec.PrimaryEntityNameGet = delegate() { return "account"; };
  6.  
  7. var parametercollection = new ParameterCollection();
  8. var accountentity = new Entity("account");
  9. accountentity.Attributes.Add("telephone1", "123456"); ;
  10.  
  11. parametercollection.Add("Target", accountentity);
  12. pluginec.InputParametersGet = delegate() { return parametercollection; };

Line number 2-4 provides your own implementations of various properties because these are used in the constructor of your plug-in as shown below

  1. base.RegisteredEvents.Add(new Tuple<int, string, string, Action<LocalPluginContext>>(10, "Create", "account", new Action<LocalPluginContext>(ExecutePreValidateAccountCreate)));

Lines 5-12 stubs properties as required by your plug-in code, especially InputParameters propery, “Target” indexer and “telephone1” attribute.

  1. var account = localContext.PluginExecutionContext.InputParameters.ContainsKey("Target") ?
  2.     localContext.PluginExecutionContext.InputParameters["Target"] as Entity : null;
  3. if (account == null) return;
  4. if (!account.Attributes.ContainsKey("telephone1")) return;
  5. string phonenumber = account.Attributes["telephone1"] as string;

It is time to provide a stub implementation for IOrganizationServiceFactory

  1. var orgservice = new SIOrganizationService();
  2.  
  3. var orgsf = new SIOrganizationServiceFactory();
  4. orgsf.CreateOrganizationServiceNullableOfGuid = delegate(Guid? userid) { return orgservice; };

CRM uses IOrganizationServiceFactory interface’s CreateOrganizationService method to return an implementation of IOrganizationService interface. IOrgnizationService interface as well as CreaeOrganizatinService method are stubbed as shown in the above code sample.

It is time to run your test method. However, if you look at the phone number “12345”, you already know that your code is going to throw a InvalidPluginExecutionException. Add the ExpectedException attribute to your test method so that it knows what exception to expect. Your test method should like

  1. [TestMethod]
  2. [ExpectedException(typeof(InvalidPluginExecutionException))]
  3. public void TestPreValidateAccountCreate()
  4. {
  5.     var serviceprovider = new SIServiceProvider();
  6.  
  7.     var pluginec = new SIPluginExecutionContext();
  8.     pluginec.UserIdGet = delegate() { return new Guid(); };
  9.     pluginec.StageGet = delegate() { return 10; };
  10.     pluginec.MessageNameGet = delegate() { return "Create"; };
  11.     pluginec.PrimaryEntityNameGet = delegate() { return "account"; };
  12.  
  13.     var parametercollection = new ParameterCollection();
  14.     var accountentity = new Entity("account");
  15.     accountentity.Attributes.Add("telephone1", "123456"); ;
  16.  
  17.     parametercollection.Add("Target", accountentity);
  18.     pluginec.InputParametersGet = delegate() { return parametercollection; };
  19.  
  20.     var orgservice = new SIOrganizationService();
  21.  
  22.     var orgsf = new SIOrganizationServiceFactory();
  23.     orgsf.CreateOrganizationServiceNullableOfGuid = delegate(Guid? userid) { return orgservice; };
  24.  
  25.     serviceprovider.GetServiceType = delegate(Type type)
  26.     {
  27.         if (type.Equals(typeof(IPluginExecutionContext)))
  28.             return pluginec;
  29.         else if (type.Equals(typeof(IOrganizationServiceFactory)))
  30.             return orgsf;
  31.         return null;
  32.     };
  33.  
  34.     var plugin = new PreValidateAccountCreate();
  35.     plugin.Execute(serviceprovider);
  36. }

Right-click your test method and run it inside visual studio. Observe the Test Results window to see whether it passed. You can add another test method with a valid US phone number to test the success code path of your plug-in. Alternately you can use Microsoft Pex for parameterizing your input values and create PUTs (Parameterized Unit Tests).