What is Fakes? Unit Testing in Dynamics 365 CE Plugins

Unit testing of CRM plugins is always a challenge and if the plugin is used for interface and connecting interface of other application is not ready, it becomes even more challenging. How to pass values to plugins and check if our code is working as expected? Here is the need to test the plugins in isolation.

Now let us see how isolation is possible. Let us discuss with a simple example. We have a plugin which is expected to run on Account entity’s Create message and it sets the Business Unit of the account based on the business unit of the user who is creating the account. We want to test this plugin, even before registering this to the CRM organisation. Hence we need to understand how we can pass the user object, account object to this plugin to test it.

Microsoft provides testing framework called Microsoft Fakes. This  feature  is highly integrated with Visual studio and is available since Visual studio 2012. Microsoft Fakes comes in two features , Stub and Shim.

As per Microsoft definitions are below

  • stub replaces a class with a small substitute that implements the same interface. To use stubs, you have to design your application so that each component depends only on interfaces, and not on other components. (By “component” we mean a class or group of classes that are designed and updated together and typically contained in an assembly.)
  • shim modifies the compiled code of your application at run time so that instead of making a specified method call, it runs the shim code that your test provides. Shims can be used to replace calls to assemblies that you cannot modify, such as .NET assemblies.

Let us go back to our example of plugin and see how we can  isolate this plugin for testing.

In the CRM plugin we have below code, just a small retrieval of business unit based on the account owner and assigning it to the account business unit.

try
{
businessUnit = RetrieveUserBusinessUnitId(targetAccount.OwnerId.Id, serviceContext);

if (businessUnit != null)
{
targetAccount.new_BusinessUnit = businessUnit;
}

}

private EntityReference RetrieveUserBusinessUnitId(Guid ownerId, CrmServiceContext serviceContext)
{
var businessUnit = (from o in serviceContext.SystemUserSet
where o.Id == ownerId
select o.BusinessUnitId).FirstOrDefault();
return businessUnit;
}

Above code gets the account owner from the target entity of the plugin context and then connects to CRM service to retrieve the business unit of the account owner. Hence to test this plugin, we should be able to isolate this plugin and pass these two values  by writing the some pseudo code.

Let us see how we work on isolation.

I am not detailing here on how to add the Fakes project in our solution so that we can add the unit test cases classes, but focusing on the coding.

Once we add the Unit testing project and added necessary assemblies, this is how the code would look like

using Microsoft.VisualStudio.TestTools.UnitTesting;

using Microsoft.Xrm.Sdk.Fakes;

using System.Fakes;

Above assembly referenced are for Fakes and then we need to add rest of the required references for our execution. We are considering mynamespace and myplugin as the namespace and plugin names here.

Below code is required in every fakes class, similar to the default code for plugin writing. The Testclass  contains stub service provider, stub plugin execution context and stub organization service against the actual objects in the plugin.

namespace mynamespace.UnitTest

{

    [TestClass]

    public class myPluginUnitTest

    {

        private Entity TestEntity { get; set; }

        private static StubIServiceProvider ServiceProvider { get; set; }

        private static StubIPluginExecutionContext PluginExecutionContext { get; set; }

        private static StubIOrganizationService OrganizationService { get; set; }

        public myPluginUnitTest()

       {

        }

        private TestContext testContextInstance;

        /// <summary>

        ///Gets or sets the test context which provides information about and functionality for the current test run.

        ///</summary>

        public TestContext TestContext

        {

            get

            {

                return testContextInstance;

            }

            set

            {

                testContextInstance = value;

            }

        }

ClassInitialize initializes the instances of org service, context and also adds user to the context object.

        [ClassInitialize]

        public static void ClassInit(TestContext textContext)

        {

            var context = new StubIPluginExecutionContext();

            var tracingService = new StubITracingService();

            var orgFactory = new StubIOrganizationServiceFactory();

            ServiceProvider = new StubIServiceProvider();

            OrganizationService = new StubIOrganizationService();

            PluginExecutionContext = context;

            //override GetService behaviour and return our stubs

            ServiceProvider.GetServiceType =

                (type) =>

                {

                    return HC.GetType(type, context, tracingService, orgFactory);

                };

            context.UserIdGet = () => Guid.Empty;

            //return our stub organizationservice

            orgFactory.CreateOrganizationServiceNullableOfGuid = (userId) => OrganizationService;

            //write trace logs to output. only works when debugging tests

            tracingService.TraceStringObjectArray = (message, args) => Debug.WriteLine(message, args);

        }

TestInitialize function informs the message of the plugin execution context

        [TestInitialize]

        public void TestInit()

        {

            //setup initial values for each test

            PluginExecutionContext.MessageNameGet = () => “Create”;

        }

Below code cleans up the objects

        [TestCleanup]

        public void TestCleanup()

        {

            TestEntity = null;

        }

Then comes the actual TestMethod which will get executed for testing

Here we have to add all required objects which are expected in the plugin code.We are creating the business unit object which is expected by the plugin.We also add attributes to the target  of plugin execution context

[TestMethod]
public void Myplugin()
{
Account targetacc = new Account();
targetacc.Id = new Guid();
targetacc.OwnerId = new EntityReference(Account.EntityLogicalName, new Guid(“5835DE9E-4962-E811-A95E-000D3A361C23”));

targetacc.new_BusinessUnit = new EntityReference(Account.EntityLogicalName, new Guid(“5835DE9E-4962-E811-A95E-000D3A361C23”));

var t = new ParameterCollection();
t.Add(“Target”, targetacc);
PluginExecutionContext.InputParametersGet = () => t;
PluginExecutionContext.MessageNameGet = () => “Create”;
PluginExecutionContext.StageGet = () => 20;
PluginExecutionContext.PrimaryEntityIdGet = () => Guid.NewGuid();
PluginExecutionContext.PrimaryEntityNameGet = () => Account.EntityLogicalName;

We are creating a user entity object with business unit entity reference added to it. We will pass this object as response to the Linq statement in RetrieveuserbusinessunitID

var Sysusrentity = new SystemUser();
Sysusrentity.Id = Guid.NewGuid();
Sysusrentity.BusinessUnitId = new EntityReference(SystemUser.EntityLogicalName, new Guid(“5835DE9E-4962-E811-A95E-000D3A361C23”));

Here is how the object is passed as response to the query.

OrganizationService.ExecuteOrganizationRequest = orgReq =>
{
RetrieveMultipleResponse response = null;

if (((Microsoft.Xrm.Sdk.Query.QueryExpression)((RetrieveMultipleRequest)orgReq).Query).EntityName
== SystemUser.EntityLogicalName)
{
List<Entity> entities = new List<Entity> { Sysusrentity };
response = new RetrieveMultipleResponse
{
   Results =
   new ParameterCollection
   {
  “EntityCollection”,
  new EntityCollection
  (entities)
   }
};
return response;
}
return new OrganizationResponse();
};

CallPluginFakes();

}

CallPluginFakes function has reference of the actual plugin to be executed for the testing and also has definitions for the parameters used in plugin registration tool such as  secure and unsecure configurations

public void CallPluginFakes()

        {

            string expectedExceptionMessage = “”;

            string actualThrownExceptionMessage = “”;

            string unsecureConfig = “”;

            string secureConfig = “”;

            try

            {

                var plugin = new myplugin(unsecureConfig, secureConfig);

                plugin.Execute(ServiceProvider);

            }

            catch (InvalidPluginExecutionException ex)

            {

                actualThrownExceptionMessage = ex.Message;

            }

With above simple example, we have seen how Fakes can be used for Unit testing of CRM plugins.

There are other scenarios which might have to be handled in Fakes class such as Retrieve, RetrieveMultiple, Execute etc,

We can see those in details later posts.

Isolate code under test with Microsoft Fakes

Fake XRM Easy versus other testing frameworks

NIstant Rana’s post about Fakes

 

 

 

 

 

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.