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)

Advertisements

8 comments on “Dynamics CRM 2011 Unit Test Part 2: Microsoft Fakes with LINQ query

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

  2. Pingback: Dynamics CRM 2011 Unit Test Part 5: Microsoft Fakes with LINQ query through IOrganizationService | Zhongchen Zhou's Blog

  3. Pingback: Dynamics CRM 2011 Unit Test Part 5: Microsoft Fakes with LINQ query through IOrganizationService - Zhongchen Zhou's Dynamics CRM Tips, Tricks and Portal Development - CRM Technical Blogs - Microsoft Dynamics Community

  4. Hi,
    Probably lame question, but… how should I generate fake assembly for System.Core? I cannot add this assembly to project references because VS informs be that that reference is automatically added during compilation.
    Best regards,
    Peter

    • You can add that to non-test project and generate the fake assembly from that project and manually add reference to the fake assembly from test project

  5. 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