Dela via


Unit Testing Microsoft Dynamics CRM 2011 Plug-ins with Microsoft Fakes

Visual Studio 2012 Ultimate ships with a new isolation framework called Microsoft Fakes. This article describes how we can use Microsoft Fakes to unit test plug-in code without a dependency on a Microsoft Dynamics CRM 2011 server, allowing us to easily test logic in isolation, from a known state.  This post assumes a certain level of familiarity in coding Microsoft Dynamics CRM 2011 plug-ins.

Why unit test?

Bugs are an inevitable side effect of any software development. As software evolves, changes in code can lead to unintentional side effects. Bugs identified during development are much easier and cheaper to fix than those discovered during later stages of the application lifecycle. Although there is a certain level of investment required to build a suite of unit tests, once built they are a valuable asset for the on-going early detection of bugs.

What’s the challenge?

There are a number of challenges in writing unit tests for Microsoft Dynamics CRM plug-ins, generally introduced by external dependencies. For example, plug-in logic is typically heavily dependent on the contextual information passed into the plug-in by Microsoft Dynamics CRM when the plug-in is triggered. Any unit test that invokes plug-in logic must somehow pass in the required information.

Similarly, plug-in code commonly interacts with the Microsoft Dynamics CRM server using the IOrganizationService object to query and modify Microsoft Dynamics CRM data, so it’s common for plug-in logic to become heavily dependent on the state of that data. To write effective unit tests, this data dependency needs to be removed.

Also, plug-in logic is commonly dependent on logic stored in external assemblies. For example, a plug-in may reference the System.DateTime.Now property, and change its behaviour dependent on the returned DateTime value. This value is obviously dependent on the time at which the code is executed, so any unit test needs to have some way to overcome this.

This is not an exhaustive list. Plug-ins can be dependent on all sorts of external dependencies; web services, databases, local files and registry settings to name a few, but the concept is always the same; to effectively unit test, we need to be able to run the code in complete isolation.

Enter Microsoft Fakes

Microsoft Fakes allows us to address these types of challenges by providing a way of simulating external behaviour without modifying our original logic. Let’s consider a trivial plug-in to demonstrate how this can be achieved.

Example 1 –A plug-in that sets a field value

Imagine you have a requirement to write a plug-in that sets the value of a field during record creation. We may start with an implementation such as the one below, which could be triggered by the Post Create event of the entity in question:

Code Snippet

  1. public void Execute(IServiceProvider serviceProvider)
  2.         {
  3.             var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
  4.             var entity = (Entity)context.InputParameters["Target"];
  5.             entity["new_pluginexecuted"] = true;
  6.         }

 

The implementation obtains the IPluginExecutionContext object from the IServiceProvider object passed in by Dynamics CRM. From there, we are able to access the record being created, and set a value accordingly.

We can now write a test method in a separate unit test project to check that this execution produces the expected outcome. Rather than writing a custom class that implements IServiceProvider, we can use the new Fakes functionality to create stubs of the required objects, and test that the plug-in code performs correctly.

Create a unit test project through the new project wizard, you’ll find the project template in the Visual C# -> Test area (or Visual Basic – this walkthrough assumes C#). In the new project, add a reference to the plug-in project, and to Microsoft.Xrm.Sdk.dll and System.Runtime.Serialization.dll. If you right click the newly added Microsoft.Xrm.Sdk.dll assembly (or any other referenced assembly), you’ll notice the option to “Add Fakes Assembly”. Click this option, and you should see a new reference to Microsoft.Xrm.Sdk.5.0.0.0.Fakes assembly. Also select this option for System, which should create a System.4.0.0.0.Fakes reference.

image

 image

Locate the TestMethod1() method within the UnitTest1.cs class file. You can rename the class and method if you wish. In this method, we’ll create a number of stub objects, and then pass them into the plug-in method to test the functionality.

Our plug-in’s Execute method needs a System.IServiceProvider parameter. Because we’ve faked the System assembly, we already have a class that we can use – System.Fakes.StubIServiceProvider. This class is interchangeable with the System.IServiceProvider (so we can pass an instantiated StubIServiceProvider into the Execute method), and allows us to specify a delegate that is executed when properties or methods are invoked. We can set properties on this object to return other stub objects, ultimately building up an object that will respond to method calls executed by our plug-in in a predictable way. To demonstrate this, let’s have a look at what our plug-in will do.

  1. Call the GetService method on the IServiceProvider object, passing in the IPluginExecutionContext type as a parameter, and receive an IPluginExecutionContext object
  2. Access the InputParameters indexed property, passing the string parameter “Target”, and cast the returned value to an Entity object.
  3. Set the “new_pluginexecuted” attribute on the entity to true.

The following test code sets up the StubIServiceProvider object with just enough state to react to the above logic in a manner that will test the effect of the plug-in code.

Code Snippet

  1. var serviceProvider = new StubIServiceProvider();
  2.  
  3. var context = new StubIPluginExecutionContext();
  4.  
  5. serviceProvider.GetServiceType =
  6.     (type) =>            
  7.     {
  8.         if (type == typeof(IPluginExecutionContext))
  9.         {
  10.             return context;
  11.         }
  12.         else
  13.         {
  14.             return null;
  15.         }
  16.     };
  17.  
  18. var inputParameters = new ParameterCollection();
  19. context.InputParametersGet = () => { return inputParameters; };
  20.  
  21. var testEntity = new Entity();
  22. inputParameters.Add(new KeyValuePair<string,object>("Target", testEntity));
  23.  
  24. var plugin = new SetPluginExecutedPlugin();

 

After we’ve arranged the objects, we can perform the action we wish to test, which in this case is the Execute method. Following this execution, we can check our test objects have been affected in the right way using assertion methods. Here’s the complete test class:

Code Snippet

  1. using System.Collections.Generic;
  2. using System.Fakes;
  3. using Microsoft.VisualStudio.TestTools.UnitTesting;
  4. using Microsoft.Xrm.Sdk;
  5. using Microsoft.Xrm.Sdk.Fakes;
  6.  
  7. namespace Test_Plugin.Tests.Unit
  8. {
  9.     [TestClass]
  10.     public class SetPluginExecutedPluginUnitTests
  11.     {
  12.         [TestMethod]
  13.         public void TestMethod1()
  14.         {
  15.             //Arrange
  16.             var serviceProvider = new StubIServiceProvider();
  17.  
  18.             var context = new StubIPluginExecutionContext();
  19.  
  20.             serviceProvider.GetServiceType =
  21.                 (type) =>            
  22.                 {
  23.                     if (type == typeof(IPluginExecutionContext))
  24.                     {
  25.                         return context;
  26.                     }
  27.                     else
  28.                     {
  29.                         return null;
  30.                     }
  31.                 };
  32.  
  33.             var inputParameters = new ParameterCollection();
  34.             context.InputParametersGet = () => { return inputParameters; };
  35.  
  36.             var testEntity = new Entity();
  37.             inputParameters.Add(new KeyValuePair<string,object>("Target", testEntity));
  38.  
  39.             var plugin = new SetPluginExecutedPlugin();
  40.  
  41.             //Act
  42.             plugin.Execute(serviceProvider);
  43.  
  44.             //Assert
  45.             Assert.AreEqual<bool>(true, testEntity.GetAttributeValue<bool>("new_pluginexecuted"), "Set new_pluginexecuted attribute value was not expected");
  46.         }
  47.     }
  48. }

 

My assertion in this test is quite straightforward; I am looking at the Entity object I set up, and checking that the field has been set to true. Running this unit test indicates success, and this plug-in hasn’t even been deployed to a CRM server yet.

image

Unit test results

The fact that my unit test has run successfully does not mean my plug-in is correct. I could have made some logical error in my plug-in code that has an unintended side effect, or even have logical errors in my test code. The test merely indicates that it affects my prepared stub objects in the way I expected. The value in the unit test is in the fact that I now have some code that consumes my plug-in code, which will always execute in a predictable way with no external dependencies. Other testing that invokes my plug-in code should be performed, and ideally if a bug is found, more unit tests should be written to expose the bug for continued retesting at a later date.

Example 2 – A more typical plug-in

Let’s consider a slightly more complex plug-in which may be used in a real system. Suppose your customer has a requirement to prevent cases being re-activated more than 30 days after they are have been closed. This could be implemented with the following code (for the purposes of this example, imagine a different plug-in sets the value of new_dateclosed when case is closed).

Code Snippet

  1. public void Execute(IServiceProvider serviceProvider)
  2. {
  3.     var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
  4.     var entity = context.InputParameters["Target"] as Entity;
  5.  
  6.     //Establish case reactivation is being attempted - omitted to simplify example
  7.             
  8.     var dateClosed = entity.GetAttributeValue<DateTime>("new_dateclosed");
  9.     if (dateClosed.AddDays(30) < DateTime.Now)
  10.     {
  11.         throw new InvalidPluginExecutionException("This case has been closed for too long to reopen.");
  12.     }
  13. }

 

We can write a unit test similar to the last example to ascertain whether the exception is correctly thrown under the right conditions, and we can control the value of new_dateclosed very easily.

Here’s an example of two unit tests that test these conditions. TestMethod1 expects an exception (note the [ExpectedException] method attribute), and TestMethod2 does not. The code is slightly differently structured here, as I’ve used TestInitialize and TestCleanup methods to setup and tear down my tests, saving me repeating the context setup code.

Code Snippet

  1. [TestClass]
  2. public class PreventCaseReopenPluginUnitTest
  3. {
  4.     private StubIServiceProvider ServiceProvider { get; set; }
  5.     private Entity TestEntity { get; set; }
  6.  
  7.     [TestInitialize]
  8.     public void Init()
  9.     {
  10.         var context = new StubIPluginExecutionContext();
  11.  
  12.         ServiceProvider = new StubIServiceProvider();
  13.         ServiceProvider.GetServiceType =
  14.             (type) =>
  15.             {
  16.                 if (type == typeof(IPluginExecutionContext))
  17.                 {
  18.                     return context;
  19.                 }
  20.                 else
  21.                 {
  22.                     return null;
  23.                 }
  24.             };
  25.  
  26.         var inputParameters = new ParameterCollection();
  27.         context.InputParametersGet = () => { return inputParameters; };
  28.  
  29.         TestEntity = new Entity();
  30.         inputParameters.Add(new System.Collections.Generic.KeyValuePair<string, object>("Target", TestEntity));
  31.     }
  32.  
  33.     [TestCleanup]
  34.     public void Cleanup()
  35.     {
  36.         ServiceProvider = null;
  37.         TestEntity = null;
  38.     }
  39.  
  40.     [TestMethod]
  41.     [ExpectedException(typeof(InvalidPluginExecutionException), "Exception not thrown")]
  42.     public void TestMethod1()
  43.     {
  44.         TestEntity["new_dateclosed"] = new DateTime(2010, 1, 1);
  45.         var plugin = new PreventCaseReopenPlugin();
  46.  
  47.         plugin.Execute(ServiceProvider);
  48.     }
  49.  
  50.     [TestMethod]
  51.     public void TestMethod2()
  52.     {
  53.         TestEntity["new_dateclosed"] = new DateTime(2012, 11, 1);
  54.         var plugin = new PreventCaseReopenPlugin();
  55.  
  56.         plugin.Execute(ServiceProvider);            
  57.     }
  58. }

 

However, there is another subtle dependency in this code; DateTime.Now. The value returned by this property varies depending on the date and time that the code is run, which means our unit tests may behave differently depending on when we run them. Both tests pass now, but TestMethod2 will fail if I run the unit test a couple of months from now. To complicate matters, DateTime.Now is a static property, so we cannot use a dependency injection approach to alter the behaviour of the property. To solve this problem without altering the plug-in code, we need to use a shim.

Shims

Like stubs, shims can also be used to alter method behaviour, but work by intercepting method calls at runtime, rather than being injected into method calls as parameters. By using the ShimDateTime class (generated by faking the System assembly earlier on), we can redirect calls to DateTime.Now to our own lambda expression.

The following code demonstrates this approach, ensuring that any call to DateTime.Now within the context of the ShimsContext returns a fixed DateTime object.

Code Snippet

  1. [TestMethod]
  2. public void TestMethod3()
  3. {
  4.     TestEntity["new_dateclosed"] = new DateTime(2012, 11, 1);
  5.     var plugin = new PreventCaseReopenPlugin();
  6.  
  7.     using (ShimsContext.Create())
  8.     {
  9.         System.Fakes.ShimDateTime.NowGet = () => {
  10.             return new DateTime(2012, 1, 15);
  11.         };
  12.         plugin.Execute(ServiceProvider);
  13.     }
  14. }

Conclusion

The techniques here can be extended with relative ease to simulate IOrganizationService operations, database calls, external web service calls and beyond. A suite of unit tests is a valuable asset to a developer writing code that is susceptible to change in the future, and the new Microsoft Fakes isolation framework included in Visual Studio 2012 Ultimate allows developers to simplify the process of writing test code that effectively tests plug-in logic.

Dave

Dave Burman Consultant Microsoft Consulting Services UK

View my bio