Dynamics CRM 2011 Unit Test Part 3: Microsoft Fakes with OrganizationServiceContext

Dynamics CRM 2011 Unit Test Part 1: Introduction and Series Contents

The complete sample code using Microsoft Fakes can be downloaded from MSDN sample gallery: Dynamics CRM unit test using Microsoft Fakes

If you know IOrganizationService API and underlining messages, there is also an indirect approach to unit test OrganizationServiceContext, which is to mock the underlining IOrganizationService. That is discussed in another post (Dynamics CRM 2011 Unit Test Part 6: Microsoft Fakes with OrganizationServiceContext through IOrganizationService).

OrganizationServiceContext

OrganizationServiceContext is based on the same concept as the ObjectContext Class in Entity Framework. The OrganizationServiceContext class lets you track changes, manage identities and relationships. Objects tracked by the organization service context are instances of entity types that represent data in Microsoft Dynamics CRM. You can designate actions to be performed on these objects and the service context tracks the changes. When the SaveChanges method is called, the service context instructs Microsoft Dynamics CRM to generate commands to create, update or delete the entities the tracked objects represent.

For more information on OrganizationServiceContext, see Use the Organization Service Context Class and OrganizationServiceContext Class

The main method that we want to simulate is OrganizationServiceContext.SaveChanges(). We will need to create fakes assembly for microsoft.xrm.sdk assembly using visual studio 2012, after that we can use Microsoft.Xrm.Sdk.Client.Fakes.ShimOrganizationServiceContext type contained within the fake assembly to detour call to OrganizationServiceContext.SaveChanges().

var fakeContext = new Microsoft.Xrm.Sdk.Client.Fakes.ShimOrganizationServiceContext(context);
fakeContext.SaveChanges = () =>
 {
 entity.Id = expected;

 // SaveChangesResultCollection only has one internal constructor
 // so we can not create a instance outside of Microsoft.Xrm.Sdk assembly
 // we will use reflection to create instances of SaveChangesResultCollection and SaveChangesResult
 var results = CreateSaveChangesResultCollection(SaveChangesOptions.None);

var request = new OrganizationRequest();
 var response = new OrganizationResponse();

results.Add(CreateSaveChangesResult(request, response));

return results;
 };

It should be very straightforward, except that SaveChangesResult and SaveChangesResultCollection only have internal constructors, we have to use reflection to create  SaveChangesResult and SaveChangesResultCollection that returned from OrganizationServiceContext.SaveChanges().

Create SaveChangesResultCollection

private SaveChangesResultCollection CreateSaveChangesResultCollection(SaveChangesOptions option)
 {
 var con = typeof(SaveChangesResultCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault();
 var results = con.Invoke(new object[] { option }) as SaveChangesResultCollection;
 return results;
 }

Create SaveChangesResult

private SaveChangesResult CreateSaveChangesResult(OrganizationRequest request, OrganizationResponse response)
 {
 var con = typeof(SaveChangesResult).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault();
 var result = con.Invoke(new object[] { request, response }) as SaveChangesResult;
 return result;
 }

private SaveChangesResult CreateSaveChangesResult(OrganizationRequest request, Exception error)
 {
 var con = typeof(SaveChangesResult).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault();
 var result = con.Invoke(new object[] { request, error }) as SaveChangesResult;
 return result;
 }

Suppose we use the following code to create account in crm

public class ContextMethods
 {
 private OrganizationServiceContext _context;

public ContextMethods(OrganizationServiceContext context)
 {
 _context = context;
 }

public Guid CreateAccount(string name)
 {
 var entity = new Entity("account")
 {
 Attributes = new Microsoft.Xrm.Sdk.AttributeCollection
 {
 { "name", name }
 }
 };

_context.AddObject(entity);

var results = _context.SaveChanges();

return entity.Id;
 }
 }

The unit test code could be

[TestClass]
 public class ContextMethodsTest
 {
 [TestMethod]
 public void CreateAccountTest()
 {
 //
 // Arrange
 //
 var connection = new CrmConnection("Crm");
 var service = new OrganizationService(connection);
 var context = new OrganizationServiceContext(service);
 using (ShimsContext.Create())
 {
 string accountName = "abcabcabc";
 Guid actual;
 Guid expected = Guid.NewGuid();

int callCount = 0;
 Entity entity = null;

var fakeContext = new Microsoft.Xrm.Sdk.Client.Fakes.ShimOrganizationServiceContext(context);

fakeContext.AddObjectEntity = e =>
 {
 callCount++;
 entity = e;
 };

fakeContext.SaveChanges = () =>
 {
 entity.Id = expected;

 // SaveChangesResultCollection only has one internal constructor
 // so we can not create a instance outside of Microsoft.Xrm.Sdk assembly
 // we will use reflection to create instances of SaveChangesResultCollection and SaveChangesResult
 var results = CreateSaveChangesResultCollection(SaveChangesOptions.None);

var request = new OrganizationRequest();
 var response = new OrganizationResponse();

results.Add(CreateSaveChangesResult(request, response));

return results;
 };

ContextMethods target = new ContextMethods(context);

//
 // Act
 //
 actual = target.CreateAccount(accountName);

//
 // Assert
 //
 Assert.AreEqual(callCount, 1); // verify OrganizationServiceContext.AddObject is called once
 Assert.IsNotNull(entity); // verify OrganizationServiceContext.AddObject is called with not null object
 Assert.AreEqual(entity.LogicalName, "account"); // verify OrganizationServiceContext.AddObject is called with entity with proper entity name
 Assert.AreEqual(entity.GetAttributeValue<string>("name"), accountName); // verify OrganizationServiceContext.AddObject is called with entity with proper value set on name attribute

 Assert.AreEqual(expected, actual);
 }
 }

private SaveChangesResultCollection CreateSaveChangesResultCollection(SaveChangesOptions option)
 {
 var con = typeof(SaveChangesResultCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault();
 var results = con.Invoke(new object[] { option }) as SaveChangesResultCollection;
 return results;
 }

private SaveChangesResult CreateSaveChangesResult(OrganizationRequest request, OrganizationResponse response)
 {
 var con = typeof(SaveChangesResult).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault();
 var result = con.Invoke(new object[] { request, response }) as SaveChangesResult;
 return result;
 }

private SaveChangesResult CreateSaveChangesResult(OrganizationRequest request, Exception error)
 {
 var con = typeof(SaveChangesResult).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault();
 var result = con.Invoke(new object[] { request, error }) as SaveChangesResult;
 return result;
 }
 }

Advertisements

6 comments on “Dynamics CRM 2011 Unit Test Part 3: Microsoft Fakes with OrganizationServiceContext

  1. Pingback: Dynamics CRM 2011 Unit Test Part 1: Introduction and Series Contents | Zhongchen Zhou's Blog

  2. Pingback: Part 6: Microsoft Fakes with OrganizationServiceContext through IOrganizationService | Zhongchen Zhou's Blog

  3. Pingback: Part 6: Microsoft Fakes with OrganizationServiceContext through IOrganizationService - Zhongchen Zhou's Dynamics CRM Tips, Tricks and Portal Development - CRM Technical Blogs - Microsoft Dynamics Community

  4. Pingback: Information to Get started with Unit Testing with Microsoft Fakes and CRM – Hosk's Dynamic CRM Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s