In parts one and two of this series, I gave an introduction to unit testing Dynamics CRM C# interfaces code with mock objects using NUnit and Moq, and I showed code samples for a couple of different scenarios. In this post I will show how to work with CRM metadata (optionset values, statuscode values, etc.) in your unit tests.
One limitation we run into when using Moq (and Rhino.Mocks, too) with the CRM SDK is that many of the objects that the CRM service returns are instances of sealed classes (looking at you, Xrm.Sdk.Messages namespace), so we can't set up useful mock responses. While this wasn't a problem for the code samples in my earlier posts on the topic, it's something you will run into if you try to put together comprehensive tests.
To illustrate, let's say you have a function called GetPicklistOptionCount that returns a count of a picklist's optionset values.
public static int GetPicklistOptionCount(string entityName, string picklistName, IOrganizationService service)
{
RetrieveAttributeRequest retrieveAttributeRequest = new RetrieveAttributeRequest
{
EntityLogicalName = entityName,
LogicalName = picklistName,
RetrieveAsIfPublished = true
};
// Execute the request.
RetrieveAttributeResponse retrieveAttributeResponse = (RetrieveAttributeResponse)service.Execute(retrieveAttributeRequest);
// Access the retrieved attribute.
PicklistAttributeMetadata retrievedPicklistAttributeMetadata = (PicklistAttributeMetadata)retrieveAttributeResponse.AttributeMetadata;
OptionMetadata[] optionList = retrievedPicklistAttributeMetadata.OptionSet.Options.ToArray();
return optionList.Length;
}
The code snippet below intuitively seems like it would be a good way to set up a mock:
//instantiate an optionset to hold some mock metadata
PicklistAttributeMetadata retrievedPicklistAttributeMetadata = new PicklistAttributeMetadata();
OptionMetadata femaleOption = new OptionMetadata(new Label("Female", 1033), 43); //as with all our mocks, the actual values don't matter so long as they're consistent throughout the test
femaleOption.Label.UserLocalizedLabel = new LocalizedLabel("Female", 1033);
femaleOption.Label.UserLocalizedLabel.Label = "Female";
OptionMetadata maleOption = new OptionMetadata(new Label("Male", 1033), 400);
maleOption.Label.UserLocalizedLabel = new LocalizedLabel("Male", 400);
maleOption.Label.UserLocalizedLabel.Label = "Male";
OptionSetMetadata genderOptionSet = new OptionSetMetadata
{
Name = "gendercode",
DisplayName = new Label("Gender", 1033),
IsGlobal = true,
OptionSetType = OptionSetType.Picklist,
Options = { femaleOption, maleOption }
};
retrievedPicklistAttributeMetadata.OptionSet = genderOptionSet;
//instantiate a new RetrieveAttributeResponse to return in the mock
RetrieveAttributeResponse response = new RetrieveAttributeResponse();
//set the metadata of our response object
response.AttributeMetadata = retrievedPicklistAttributeMetadata; //THIS WON'T WORK!
//set up the mock
mock.Setup(t => t.Execute(It.Is<RetrieveAttributeRequest>(r=>r.LogicalName=="gendercode"))).Returns(response);
Unfortunately you will find that code doesn't compile because AttributeMetadata is a read-only property. Because RetrieveAttributeResponse is a sealed class, Moq doesn't give us a way to work around that (like maybe creating a class that inherits from RetrieveAttributeResponse). It would seem there's no way to mock the picklist optionset request/response, so we should just give up and not test this functionality, right? Wrong, the solution to our problem is to use a wrapper class.
A wrapper class is exactly what it sounds like, one class that wraps another. In this particular case, we can create a RetrieveAttributeResponseWrapper class that behaves like the original class in every way, except the AttributeMetadata property isn't read-only. Here's the wrapper class I wrote for the RetrieveAttributeResponse class:
/// <summary>
/// Wrapper class for the Xrm.Sdk.Messages.RetrieveAttributeResponse class. Primarily used to support Moq injection during testing.
/// </summary>
[DataContract(Namespace = "http://schemas.microsoft.com/xrm/2011/Contracts")]
public class RetrieveAttributeResponseWrapper : OrganizationResponse
{
private AttributeMetadata _metadata;
public RetrieveAttributeResponseWrapper(OrganizationResponse response)
{
try
{
_metadata = ((RetrieveAttributeResponseWrapper)response).AttributeMetadata;
}
catch
{
_metadata = ((RetrieveAttributeResponse)response).AttributeMetadata;
}
}
public AttributeMetadata AttributeMetadata
{
get
{
return _metadata;
}
set
{
_metadata = value;
}
}
}
A few things should immediately jump out at you. First, there's a datacontract attribute. This is exactly the same as the wrapped RetrieveAttributeResponse class. Second, the wrapper class inherits from the OrganizationResponse class. Again, this is exactly the same as the wrapped class. Next, there is a constructor that takes an invocation argument, which is the only externally visible difference from the wrapped class. This what the wrapper class uses to wrap a particular object. The constructor takes an OrganizationResponse object as the invocation argument, and then it sets a private _metadata property to the AttributeMetadata property of that object cast either as a RetrieveAttributeResponse or RetrieveAttributeResponseWrapper. This is important because the mock will return a RetrieveAttributeResponseWrapper instead of a RetrieveAttributeResponse. Finally, the wrapper class AttributeMetadata property returns the _metadata property that is set in the constructor.
You may at this point be thinking "that looks a lot like a RetrieveAttributeResponse, but close doesn't cut it when it comes to object types," and you'd be correct. The picklist optionset retrieval method will require a slight change to work with the wrapper class instead of the normal CRM class. Granted writing production code differently to account for nuances of testing is generally considered bad form, but I assure you it's OK here for two reasons. First, the change is so minor that it's almost unnoticeable. Second, a wrapper class gives you a lot of flexibility. If for some reason Microsoft decides to change the RetrieveAttributeResponse class in a future SDK release, you only have to update the wrapper class instead of updating all your code that retrieves optionset values.
Here is an updated version of the GetPicklistOptionCount method that uses the RetrieveAttributeResponseWrapper class:
public static int GetPicklistOptionCount(string entityName, string picklistName, IOrganizationService service)
{
RetrieveAttributeRequest retrieveAttributeRequest = new RetrieveAttributeRequest
{
EntityLogicalName = entityName,
LogicalName = picklistName,
RetrieveAsIfPublished = true
};
// Execute the request.
RetrieveAttributeResponseWrapper retrieveAttributeResponse = (new RetrieveAttributeResponseWrapper(service.Execute(retrieveAttributeRequest))); //this is the only change from before
// Access the retrieved attribute.
PicklistAttributeMetadata retrievedPicklistAttributeMetadata = (PicklistAttributeMetadata)retrieveAttributeResponse.AttributeMetadata;
OptionMetadata[] optionList = retrievedPicklistAttributeMetadata.OptionSet.Options.ToArray();
return optionList.Length;
}
The only change to the method is in the line where the service executes the RetrieveAttributeRequest.
Finally, here is the entire test method:
[Test]
public void GetPicklistOptionCountTest()
{
//ARRANGE - set up everything our test needs
//first - set up a mock service to act like the CRM organization service
var serviceMock = new Mock<IOrganizationService>();
IOrganizationService service = serviceMock.Object;
PicklistAttributeMetadata retrievedPicklistAttributeMetadata = new PicklistAttributeMetadata();
OptionMetadata femaleOption = new OptionMetadata(new Label("Female", 1033), 43);
femaleOption.Label.UserLocalizedLabel = new LocalizedLabel("Female", 1033);
femaleOption.Label.UserLocalizedLabel.Label = "Female";
OptionMetadata maleOption = new OptionMetadata(new Label("Male", 1033), 400);
maleOption.Label.UserLocalizedLabel = new LocalizedLabel("Male", 400);
maleOption.Label.UserLocalizedLabel.Label = "Male";
OptionSetMetadata genderOptionSet = new OptionSetMetadata
{
Name = "gendercode",
DisplayName = new Label("Gender", 1033),
IsGlobal = true,
OptionSetType = OptionSetType.Picklist,
Options = { femaleOption, maleOption }
};
retrievedPicklistAttributeMetadata.OptionSet = genderOptionSet;
RetrieveAttributeResponseWrapper picklistWrapper = new RetrieveAttributeResponseWrapper(new RetrieveAttributeResponse());
picklistWrapper.AttributeMetadata = retrievedPicklistAttributeMetadata;
serviceMock.Setup(t => t.Execute(It.Is<RetrieveAttributeRequest>(r => r.LogicalName == "gendercode"))).Returns(picklistWrapper);
//ACT
int returnedCount = MockDemo.GetPicklistOptionCount("ANYENTITYMATCHES", "gendercode", service);
//ASSERT
Assert.AreEqual(2, returnedCount); //we expect 2 to be returned
}
Here is a zip archive with the MockDemo, MockDemoTest and RetrieveAttributeResponseWrapper classes for all the examples in this series: MockDemo.zip (2.95 kb)
Now that you know how to use wrapper classes to test interactions with the CRM SDK's sealed classes, you have all the tools you need to put together a comprehensive suite of unit tests for your Dynamics CRM C# interface code and no excuses not to. Happy coding!