Dynamics CRM 2011 Unit Test Part 4: Microsoft Fakes with CrmOrganizationServiceContext

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 CrmOrganizationServiceContext, which is to mock the underlining IOrganizationService. That is discussed in another post (Part 7: Microsoft Fakes with CrmOrganizationServiceContext through IOrganizationService).

CrmOrganizationServiceContext

CrmOrganizationServiceContext is SDK Extensions contained in Microsoft.Xrm.Client assembly. CrmOrganizationServiceContext allows the entity objects produced by the data context to participate in the WCF Data Services framework. CrmOrganizationServiceContext provide methods from various OrganizationRequest, which means a method based implementation of the message API. These methods are defined as extension methods, to use these methods we have to include the Microsoft.Xrm.Client.Messages namespace.

For more information on CrmOrganizationServiceContext, see Developer Extensions Context Object Model

Microsoft Fakes does not support CrmOrganizationServiceContext, if we try to set up a run time delegate, we will receive ShimNotSupported exception. So we can go the traditional way to extract interface and provide a wrap type around CrmOrganizationServiceContext and implement that interface.

Most of the methods contained within SDK extension are OrganizationServiceContext extension methods. These methods are defined in Microsoft.Xrm.Client.Messages.OrganizationServiceContextExtensions and Microsoft.Xrm.Client.OrganizationServiceContextExtensions. And I have created one Interface and a wrapper Class.

https://gist.github.com/3180585

https://gist.github.com/3180591


namespace CrmOrganizationServiceContextExtensions
{
 public interface ICrmOrganizationServiceContext
 {
 /*
 * Microsoft.Xrm.Client.Messages.OrganizationServiceContextExtensions
 */

/*
 * Microsoft.Xrm.Client.CrmOrganizationServiceContext
 */
 }
}

 


namespace CrmOrganizationServiceContextExtensions
{
 public class CrmContext : CrmOrganizationServiceContext, ICrmOrganizationServiceContext
 {
 public CrmContext() : base() { }
 public CrmContext(string contextName) : base(contextName) { }
 public CrmContext(CrmConnection connection) : base(connection) { }
 public CrmContext(IOrganizationService service) : base(service) { }

private Microsoft.Xrm.Sdk.Client.OrganizationServiceContext _context;
 private Microsoft.Xrm.Sdk.Client.OrganizationServiceContext Context
 {
 get
 {
 if (_context == null)
 {
 _context = this as Microsoft.Xrm.Sdk.Client.OrganizationServiceContext;
 }

return _context;
 }
 }

/*
 * Microsoft.Xrm.Client.Messages.OrganizationServiceContextExtensions
 */

/*
 * Microsoft.Xrm.Client.OrganizationServiceContextExtensions
 */
 }
}

We can start using the Interface and wrapper Class in our code.

Suppose we have the following code under test


public class CrmContextMethods
 {
 private ICrmOrganizationServiceContext _crmContext;

public CrmContextMethods(ICrmOrganizationServiceContext crmContext)
 {
 _crmContext = crmContext;
 }

public string RetrieveVersion()
 {
 var version = _crmContext.RetrieveVersion();

return version;
 }

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

var id = _crmContext.Create(entity);

return id;
 }
 }

The unit test code code could be


[TestClass]
 public class CrmContextMethodsTest
 {
 [TestMethod]
 public void CreateAccountTest()
 {
 //
 // Arrange
 //
 string accountName = "abcabcabc";
 Guid actual;
 Guid expected = Guid.NewGuid();

int callCount = 0;
 Entity entity = null;

var context = new CrmOrganizationServiceContextExtensions.Fakes.StubICrmOrganizationServiceContext();
 context.CreateEntity = e =>
 {
 callCount++;
 entity = e;
 return expected;
 };

CrmContextMethods target = new CrmContextMethods(context);

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

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

Assert.AreEqual(expected, actual);

}

[TestMethod]
 public void RetrieveVersionTest()
 {
 //
 // Arrange
 //
 string actual;
 string expected = "5.0.9690.2243";

int callCount = 0;

var context = new CrmOrganizationServiceContextExtensions.Fakes.StubICrmOrganizationServiceContext();
 context.RetrieveVersion = () =>
 {
 callCount++;
 return expected;
 };

CrmContextMethods target = new CrmContextMethods(context);

//
 // Act
 //
 actual = target.RetrieveVersion();

//
 // Assert
 //
 Assert.AreEqual(callCount, 1); // verify OrganizationServiceContext.RetrieveVersion is called once

Assert.AreEqual(expected, actual);

}
 }

Advertisements

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;
 }
 }

Dynamics CRM 2011 Unit Test Part 2: Microsoft Fakes with LINQ query

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 LINQ to CRM query, which is to mock the underlining IOrganizationService. That is discussed in another post (Dynamics CRM 2011 Unit Test Part 5: Microsoft Fakes with LINQ query through IOrganizationService).

LINQ to CRM query

Dynamics CRM 2011 supports using LINQ to query CRM data, which makes the query much more easy to write and compact. We do not need to construct a complex QueryExpression object for simple queries.

To use LINQ to query CRM data, we need to use OrganizationServiceContext or a deriving class such as CrmOrganizationServiceContext which is SDK Extensions contained in Microsoft.Xrm.Client assembly, or a deriving class created by the CrmSvcUtil tool. The OrganizationServiceContext class contains an underlying LINQ query provider that translates LINQ queries from Microsoft Visual C# or Microsoft Visual Basic .NET syntax into the query API used by Microsoft Dynamics CRM.

For more information on Build Queries with LINQ, please see Build Queries with LINQ (.NET Language-Integrated Query)

When test LINQ to CRM query, there are some challengers. First OrganizationServiceContext does not implement any interface or abstract class, second LINQ to CRM query involved some method in System.Linq namespace.


public class OrganizationServiceContext : IDisposable
public class CrmOrganizationServiceContext : OrganizationServiceContext, IInitializable, IOrganizationService, IUpdatable, IExpandProvider, IOrganizationServiceContainer
public partial class ServiceContext: Microsoft.Xrm.Sdk.Client.OrganizationServiceContext

The common method to test such class is to abstract an interface and use that interface in the calling code, but this method need to write more code and carefully design of the code. (This will be discussed in later posts with Rhino Mocks and Moq). A better approach is to use either Microsoft Fakes test framework or Microsoft Moles test framework. We will focus on Microsoft Fakes test framework in this post, Microsoft Moles will be introduced in later post.

Microsoft Fakes test framework is included in Visual Studio 2012 and can generate fakes assembly which contains shim types.  Shim types allow detouring of hard-coded dependencies on static or non-overridable methods.

For more information on Microsoft Fakes, please see Isolating Unit Test Methods with Microsoft Fakes

Let’s see a few unit test sample which use Microsoft Fakes

This sample use SDK Extensions contained in Microsoft.Xrm.Client assembly. For more information on Dynamics CRM SDK Extensions, please see SDK Extensions for Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online

We need to generate fake assembly for microsoft.xrm.sdk.dll and System.Core.dll

Enumeration

Suppose we have the following class


public class LINQToCRM
 {
 private OrganizationServiceContext _context;

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

public IEnumerable RetrieveAccountsByName(string name)
 {
 var query = from account in _context.CreateQuery("account")
 where account.GetAttributeValue("name") == name
 select account;

var accountList = new List();

foreach(var entity in query)
 {
 accountList.Add(entity);
 }

//We can simply call query.ToList()
 //foreach loop here is to demonstrate item Enumeration

return accountList;
 }
}

There are two external dependencies in this code fragment. First is OrganizationServiceContext.CreateQuery, Second is foreach(var entity in query).

In this case we have to detour OrganizationServiceContext.CreateQuery and System.Linq.EnumerableQuery<Microsoft.Xrm.Sdk.Entity>.GetEnumerator to user defined delegate.

OrganizationServiceContext.CreateQuery is the place we can simulate Dynamics CRM data store. Entities returned from this method should be considered to be retrieved from Dynamics CRM, and the result will be further filtered by the rest of the LINQ operators.

System.Linq.EnumerableQuery<Microsoft.Xrm.Sdk.Entity>.GetEnumerator is the place we return the final result for the query, regardless of the data store or any LINQ operator.


[TestMethod]
 public void RetrieveAccountsByNameTest()
 {
 var connection = new CrmConnection("Crm");
 var service = new OrganizationService(connection);
 var context = new OrganizationServiceContext(service);
 using (ShimsContext.Create())
 {
 var fakeContext = new Microsoft.Xrm.Sdk.Client.Fakes.ShimOrganizationServiceContext(context);
 LINQToCRM target = new LINQToCRM(context);

var entity1 = new Microsoft.Xrm.Sdk.Entity("account");
 entity1.Id = Guid.NewGuid();
 entity1["name"] = "abcabcabc";

var entity2 = new Microsoft.Xrm.Sdk.Entity("account");
 entity2.Id = Guid.NewGuid();
 entity2["name"] = "123123123";

var entity3 = new Microsoft.Xrm.Sdk.Entity("account");
 entity3.Id = Guid.NewGuid();
 entity3["name"] = "a1b2c3a1b2c3";

fakeContext.CreateQueryString = (entityName) =>
 {
 return new System.Linq.EnumerableQuery(new Microsoft.Xrm.Sdk.Entity[] { entity1, entity2, entity3 });
 };

IEnumerable expected = new List { entity1, entity2 };

System.Linq.Fakes.ShimEnumerableQuery.AllInstances.GetEnumerator = (a) =>
 {
 return expected.GetEnumerator();
 };

string accountName = "abcabcabc";
 IEnumerable actual;
 actual = target.RetrieveAccountsByName(accountName);

Assert.AreEqual(expected.Count(), actual.Count());
 Assert.AreEqual(expected.ElementAt(0), actual.ElementAt(0));
 Assert.AreEqual(expected.ElementAt(1), actual.ElementAt(1));
 }
 }

FirstOrDefault method

If we do not need to enumerate the result but want to call FirstOrDefault method, we can detour System.Linq.Queryable.FirstOrDefault<Microsoft.Xrm.Sdk.Entity> to user defined delegate.

Suppose we have the following code under test


public class LINQToCRM
 {
 private OrganizationServiceContext _context;

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

public Entity RetrieveAccountsById(Guid id)

{
 var query = from account in _context.CreateQuery("account")
 where account.GetAttributeValue("accountid") == id
 select account;

var accountEntity = query.FirstOrDefault();

return accountEntity;
 }
 }

The unit test would be similar to the following


[TestMethod]
 public void RetrieveAccountsByIdTest()
 {
 var connection = new CrmConnection("Crm");
 var service = new OrganizationService(connection);
 var context = new OrganizationServiceContext(service);
 using (ShimsContext.Create())
 {
 var fakeContext = new Microsoft.Xrm.Sdk.Client.Fakes.ShimOrganizationServiceContext(context);
 LINQToCRM target = new LINQToCRM(context);

fakeContext.CreateQueryString = (entityName) =>
 {
 return new System.Linq.EnumerableQuery(new Microsoft.Xrm.Sdk.Entity[] { });
 };

Guid id = Guid.NewGuid();

Microsoft.Xrm.Sdk.Entity expected = new Microsoft.Xrm.Sdk.Entity();

System.Linq.Fakes.ShimQueryable.FirstOrDefaultOf1IQueryableOfM0((a) =>
 {
 expected.Id = id;
 return expected;
 });

Microsoft.Xrm.Sdk.Entity actual;

actual = target.RetrieveAccountsById(id);

Assert.AreEqual(expected, actual);
 }
 }

Other common LINQ methods

Other common LINQ methods could also be find in System.Linq.Queryable class, so that we can use the generated shim class System.Linq.Fakes.ShimQueryable to detour required methods call to user defined delegate.

Note: Not all the methods are supported by Dynamics CRM. (e.g. Any is not supported)

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

Introduction

This is the first post of a series containing collections of patterns and code fragments showing how various unit frameworks can be used in Dynamics CRM 2011 development.

Unit test with server side components, (web service, plugin, workflow), will be explained with Microsoft Fakes, Rhino Mocks and Moq.

Unit test with client side JavaScript will be explained with QUnit.

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

Series Contents

Part 1: Introduction and Series Contents

  • Introduces the topics that will be covered in the series

Part 2: Microsoft Fakes with LINQ query

  • Unit test CRM LINQ query
  • Mock System.Linq namespace
  • Mock Mock OrganizationServiceContext.CreateQuery
  • Mock OrganizationServiceContext
  • Create SaveChangesResultCollection
  • Create SaveChangesResult

Part 4: Microsoft Fakes with CrmOrganizationServiceContext

  • Mock CrmOrganizationServiceContext

Part 5: Microsoft Fakes with LINQ query through IOrganizationService

  • Mock IOrganizationService to isolate LINQ to CRM query

Part 6: Microsoft Fakes with OrganizationServiceContext through IOrganizationService

  • Isolate method call to OrganizationServiceContext through mocking IOrganizationService

Part 7: Microsoft Fakes with CrmOrganizationServiceContext through IOrganizationService

  • Isolate method call to OrganizationServiceContext extension methods through mocking IOrganizationService
  • Isolate method call to CrmOrganizationServiceContext through mocking IOrganizationService

Part 8: Microsoft Fakes with IOrganizationService

  • Using Stub type to isolate method call to IOrganizationService

Part 9: Microsoft Fakes with CRM plugin

  •  Mock IOrganizationService, IPluginExecutionContext, ITracingService, IOrganizationServiceFactory and IServiceProvider

Part 10: Microsoft Fakes with workflow activity

  • Correctly invoke CodeActivity.Execute(CodeActivityContext executionContext)
  • Correctly pass in InArgument<T> properties
  • Pass in required service as extensions

Part 11: QUnit with client side JavaScript

  • QUnit
  • Xrm.Page Script Library Template (Microsoft Visual Studio extension)

Part 12: Rhino Mocks with LINQ query, OrganizationServiceContext and CrmOrganizationServiceContext

Part 13: Rhino Mocks with IOrganizationService

Part 14: Rhino Mocks with CRM plugin

Part 15: Rhino Mocks with workflow activity

Part 16: Moq with LINQ query, OrganizationServiceContext and CrmOrganizationServiceContext

Part 17: Moq with IOrganizationService

Part 18: Moq with CRM plugin

Part 19: Moq with workflow activity