Hi all,
In this post I will discuss the code that you can use to unit test your Dynamics CRM plugins using Visual studio unit testing framework.
You would first need to create the fake libraries for all the CRM SDK libraries as well as mscorlib and system DLLs. Make sure that the version matches with the version of the DLLs being used in the code. I have CRM 2015 SDK fakes and mscorlib/system DLL fakes here.
You can define a helper class with the following helper methods:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Fakes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Fakes;
namespace Contoso.CRM.Unittests
{
public class HelperMethodsContainer
{
/// <summary>
/// Moles the plugin variables.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <param name="pluginContext">The plugin context.</param>
/// <param name="organizationService">The organization service.</param>
/// <param name="stageNumber">The stage number.</param>
/// <param name="messageName">Name of the message.</param>
public static void MolePluginVariables(
StubIServiceProvider serviceProvider,
StubIPluginExecutionContext pluginContext,
StubIOrganizationService organizationService,
int stageNumber,
string messageName)
{
var serviceFactory = new StubIOrganizationServiceFactory();
var tracingService = new StubITracingService();
if (serviceProvider != null)
{
serviceProvider.GetServiceType = type =>
{
if (type == typeof(IPluginExecutionContext))
{
return pluginContext;
}
else if (type == typeof(ITracingService))
{
return tracingService;
}
else if (type == typeof(IOrganizationServiceFactory))
{
return serviceFactory;
}
return null;
};
}
pluginContext.DepthGet = () => 1;
pluginContext.UserIdGet = () => new Guid();
pluginContext.MessageNameGet = () => messageName;
pluginContext.StageGet = () => stageNumber;
pluginContext.InitiatingUserIdGet = () => new Guid();
pluginContext.CorrelationIdGet = () => new Guid();
pluginContext.PrimaryEntityIdGet = Guid.NewGuid;
pluginContext.IsInTransactionGet = () => true;
serviceFactory.CreateOrganizationServiceNullableOfGuid = t1 => organizationService;
tracingService.TraceStringObjectArray = Trace;
}
/// <summary>
/// Sets the mole for the Entity
/// </summary>
/// <param name="entityName">The LogicalName of the entity</param>
/// <param name="attributeValues">The attributes of the entity</param>
/// <param name="context">Object of type SIPluginExecutionContext</param>
/// <returns>Object of type Entity</returns>
public static Entity MoleEntity(string entityName, Dictionary<string, object> attributeValues, StubIPluginExecutionContext context)
{
var entity = new Entity(entityName);
entity.Attributes = new AttributeCollection();
if (attributeValues != null)
{
foreach (string key in attributeValues.Keys)
{
entity.Attributes.Add(key, attributeValues[key]);
}
}
if (context != null)
{
context.PrimaryEntityNameGet = () => entityName;
}
entity.Id = Guid.NewGuid();
return entity;
}
/// <summary>
/// Moles the PluginContext.InputParameters
/// </summary>
/// <param name="context">the mole of the IPluginExecutionContext</param>
/// <param name="inputParameterCollection">Object of type System.Dictionary</param>
public static void MolePluginInputParameters(StubIPluginExecutionContext context, Dictionary<string, object> inputParameterCollection)
{
if (inputParameterCollection != null)
{
var parameterCollection = new ParameterCollection();
foreach (var key in inputParameterCollection.Keys)
{
parameterCollection.Add(key, inputParameterCollection[key]);
}
if (context != null)
{
context.InputParametersGet = () => parameterCollection;
}
}
}
}
}
The first method "moles" the basic plugin variables which include the plugin depth, userID, message, stage, etc. The initial values can be anything unless they are required in the test logic.
The second method is used to mole the primary entity for the plugin (the entity which triggers the plugin)
The third method is used to set the input parameters for the plugin. This includes the "Target" parameter for a create/update plugin, etc.
In order to perform the actual test, we need to use the above methods to mole the basic plugin inputs. After that we also need to provide the implementations for the fake methods that will be invoked from the plugin. This is the standard way in which fakes framework works, where we provide the delegate that imitates what the function is expected to return.
Here is an example of a test class:
using System;
using System.Collections.Generic;
using System.Fakes;
using ContosoCRM.Plugins;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Fakes;
using Microsoft.Xrm.Sdk.Query;
namespace ContosoCRM.Unittests
{
[TestClass]
public class PostEmailCreateTests
{
[TestMethod]
public void PostEmailCreateCreateTaskWhenConditionsMeet()
{
var postEmailCreatePlugin = new PostEmailCreate();
var serviceProvider = new StubIServiceProvider();
var pluginContext = new StubIPluginExecutionContext();
var organizationService = new StubIOrganizationService();
// Mole the basic Plugin objects
HelperMethodsContainer.MolePluginVariables(serviceProvider, pluginContext, organizationService, 40, "Create");
var subjectField = "Alpha beta " + "test suffix";
var entityAttributes = new Dictionary<string, object>();
entityAttributes.Add("subject", subjectField);
entityAttributes.Add("regardingobjectid", new EntityReference("opportunity", Guid.NewGuid()));
entityAttributes.Add("description", "Hello world!");
Entity appointmentEntity = HelperMethodsContainer.MoleEntity("email", entityAttributes, pluginContext);
// set the entity in the parameter collection
var parameterAttributes = new Dictionary<string, object>();
parameterAttributes.Add("Target", appointmentEntity);
HelperMethodsContainer.MolePluginInputParameters(pluginContext, parameterAttributes);
organizationService.CreateEntity = entity => Guid.NewGuid();
organizationService.RetrieveStringGuidColumnSet = (s, guid, arg3) =>
{
var dealEntity = new Entity("opportunity");
dealEntity.Id = Guid.NewGuid();
dealEntity.Attributes.Add("name", "Alpha oppty");
dealEntity.Attributes.Add("ownerid", new EntityReference("systemuser", Guid.NewGuid()));
return dealEntity;
};
organizationService.RetrieveMultipleQueryBase = delegate(QueryBase query)
{
var entityCollection = new EntityCollection();
Entity entity;
if (query is QueryExpression && (query as QueryExpression).EntityName == "contoso_configuration")
{
entity = new Entity("contoso_configuration")
{
Attributes = new AttributeCollection
{
{ "contoso_name", "Alpha" },
{ "contoso_value", "4"}
}
};
entityCollection.Entities.Add(entity);
}
if (query is QueryExpression && (query as QueryExpression).EntityName == "contoso_activitycategory")
{
entity = new Entity("contoso_activitycategory")
{
Attributes = new AttributeCollection
{
{ "contoso_name", "Validation" }
}
};
entityCollection.Entities.Add(entity);
}
return entityCollection;
};
postEmailCreatePlugin.Execute(serviceProvider);
}
}
}
In this post I will discuss the code that you can use to unit test your Dynamics CRM plugins using Visual studio unit testing framework.
You would first need to create the fake libraries for all the CRM SDK libraries as well as mscorlib and system DLLs. Make sure that the version matches with the version of the DLLs being used in the code. I have CRM 2015 SDK fakes and mscorlib/system DLL fakes here.
You can define a helper class with the following helper methods:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Fakes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Fakes;
namespace Contoso.CRM.Unittests
{
public class HelperMethodsContainer
{
/// <summary>
/// Moles the plugin variables.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <param name="pluginContext">The plugin context.</param>
/// <param name="organizationService">The organization service.</param>
/// <param name="stageNumber">The stage number.</param>
/// <param name="messageName">Name of the message.</param>
public static void MolePluginVariables(
StubIServiceProvider serviceProvider,
StubIPluginExecutionContext pluginContext,
StubIOrganizationService organizationService,
int stageNumber,
string messageName)
{
var serviceFactory = new StubIOrganizationServiceFactory();
var tracingService = new StubITracingService();
if (serviceProvider != null)
{
serviceProvider.GetServiceType = type =>
{
if (type == typeof(IPluginExecutionContext))
{
return pluginContext;
}
else if (type == typeof(ITracingService))
{
return tracingService;
}
else if (type == typeof(IOrganizationServiceFactory))
{
return serviceFactory;
}
return null;
};
}
pluginContext.DepthGet = () => 1;
pluginContext.UserIdGet = () => new Guid();
pluginContext.MessageNameGet = () => messageName;
pluginContext.StageGet = () => stageNumber;
pluginContext.InitiatingUserIdGet = () => new Guid();
pluginContext.CorrelationIdGet = () => new Guid();
pluginContext.PrimaryEntityIdGet = Guid.NewGuid;
pluginContext.IsInTransactionGet = () => true;
serviceFactory.CreateOrganizationServiceNullableOfGuid = t1 => organizationService;
tracingService.TraceStringObjectArray = Trace;
}
/// <summary>
/// Sets the mole for the Entity
/// </summary>
/// <param name="entityName">The LogicalName of the entity</param>
/// <param name="attributeValues">The attributes of the entity</param>
/// <param name="context">Object of type SIPluginExecutionContext</param>
/// <returns>Object of type Entity</returns>
public static Entity MoleEntity(string entityName, Dictionary<string, object> attributeValues, StubIPluginExecutionContext context)
{
var entity = new Entity(entityName);
entity.Attributes = new AttributeCollection();
if (attributeValues != null)
{
foreach (string key in attributeValues.Keys)
{
entity.Attributes.Add(key, attributeValues[key]);
}
}
if (context != null)
{
context.PrimaryEntityNameGet = () => entityName;
}
entity.Id = Guid.NewGuid();
return entity;
}
/// <summary>
/// Moles the PluginContext.InputParameters
/// </summary>
/// <param name="context">the mole of the IPluginExecutionContext</param>
/// <param name="inputParameterCollection">Object of type System.Dictionary</param>
public static void MolePluginInputParameters(StubIPluginExecutionContext context, Dictionary<string, object> inputParameterCollection)
{
if (inputParameterCollection != null)
{
var parameterCollection = new ParameterCollection();
foreach (var key in inputParameterCollection.Keys)
{
parameterCollection.Add(key, inputParameterCollection[key]);
}
if (context != null)
{
context.InputParametersGet = () => parameterCollection;
}
}
}
}
}
The first method "moles" the basic plugin variables which include the plugin depth, userID, message, stage, etc. The initial values can be anything unless they are required in the test logic.
The second method is used to mole the primary entity for the plugin (the entity which triggers the plugin)
The third method is used to set the input parameters for the plugin. This includes the "Target" parameter for a create/update plugin, etc.
In order to perform the actual test, we need to use the above methods to mole the basic plugin inputs. After that we also need to provide the implementations for the fake methods that will be invoked from the plugin. This is the standard way in which fakes framework works, where we provide the delegate that imitates what the function is expected to return.
Here is an example of a test class:
using System;
using System.Collections.Generic;
using System.Fakes;
using ContosoCRM.Plugins;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Fakes;
using Microsoft.Xrm.Sdk.Query;
namespace ContosoCRM.Unittests
{
[TestClass]
public class PostEmailCreateTests
{
[TestMethod]
public void PostEmailCreateCreateTaskWhenConditionsMeet()
{
var postEmailCreatePlugin = new PostEmailCreate();
var serviceProvider = new StubIServiceProvider();
var pluginContext = new StubIPluginExecutionContext();
var organizationService = new StubIOrganizationService();
// Mole the basic Plugin objects
HelperMethodsContainer.MolePluginVariables(serviceProvider, pluginContext, organizationService, 40, "Create");
var subjectField = "Alpha beta " + "test suffix";
var entityAttributes = new Dictionary<string, object>();
entityAttributes.Add("subject", subjectField);
entityAttributes.Add("regardingobjectid", new EntityReference("opportunity", Guid.NewGuid()));
entityAttributes.Add("description", "Hello world!");
Entity appointmentEntity = HelperMethodsContainer.MoleEntity("email", entityAttributes, pluginContext);
// set the entity in the parameter collection
var parameterAttributes = new Dictionary<string, object>();
parameterAttributes.Add("Target", appointmentEntity);
HelperMethodsContainer.MolePluginInputParameters(pluginContext, parameterAttributes);
organizationService.CreateEntity = entity => Guid.NewGuid();
organizationService.RetrieveStringGuidColumnSet = (s, guid, arg3) =>
{
var dealEntity = new Entity("opportunity");
dealEntity.Id = Guid.NewGuid();
dealEntity.Attributes.Add("name", "Alpha oppty");
dealEntity.Attributes.Add("ownerid", new EntityReference("systemuser", Guid.NewGuid()));
return dealEntity;
};
organizationService.RetrieveMultipleQueryBase = delegate(QueryBase query)
{
var entityCollection = new EntityCollection();
Entity entity;
if (query is QueryExpression && (query as QueryExpression).EntityName == "contoso_configuration")
{
entity = new Entity("contoso_configuration")
{
Attributes = new AttributeCollection
{
{ "contoso_name", "Alpha" },
{ "contoso_value", "4"}
}
};
entityCollection.Entities.Add(entity);
}
if (query is QueryExpression && (query as QueryExpression).EntityName == "contoso_activitycategory")
{
entity = new Entity("contoso_activitycategory")
{
Attributes = new AttributeCollection
{
{ "contoso_name", "Validation" }
}
};
entityCollection.Entities.Add(entity);
}
return entityCollection;
};
postEmailCreatePlugin.Execute(serviceProvider);
}
}
}
The above unit test is for a plugin called "PostEmailCreate" which creates a task record whenever an email record is created in Dynamics CRM based on some conditions (like the subject of the email and its regarding object).
In the above it can be seen that delegates are provided to imitate the results of a retrieve organization service call ( organizationService.RetrieveStringGuidColumnSet ), a retrieve multiple organization service call ( organizationService.RetrieveMultipleQueryBase ) and a create organization request (organizationService.CreateEntity). It can also be seen that different values are being returned based on some conditions (like which entity triggered the call, etc.) in the retrieve calls.
I hope with the above you get a general idea on how to write unit tests for your plugins that you write in Dynamics CRM. Let me know in the comments if you would like to know more about unit testing your plugins in Dynamics CRM.
Happy Dynamics CRM!!
No comments:
Post a Comment