Using Fake XRM Easy for Unit Testing

Loading

1. Introduction to FakeXrmEasy

What is FakeXrmEasy?

FakeXrmEasy is an open-source mocking framework specifically designed for unit testing Dynamics 365/CE plugins, workflows, and custom code. It provides:

  • In-memory fake context that mimics Dataverse
  • No dependency on live environments
  • Support for early-bound and late-bound programming
  • Test-driven development (TDD) capabilities

Key Benefits Over Traditional Testing

Runs tests in milliseconds (no server calls)
Tests business logic in isolation
Simulates complete plugin execution pipeline
Free and actively maintained

2. Core Architecture

graph TD
    A[Your Plugin] --> B[FakeXrmEasy Context]
    B --> C[In-Memory Dataverse]
    C --> D[Test Assertions]

Supported Scenarios

  • Plugin testing
  • Custom workflow activity testing
  • Custom API testing
  • Web resource JavaScript testing (via Node.js)

3. Environment Setup

Installation (NuGet Packages)

Install-Package FakeXrmEasy -Version 2.0.0
Install-Package FakeXrmEasy.Messages -Version 2.0.0 # For advanced scenarios

Basic Test Project Structure

/MyPluginTests
  ├── /Entities
  ├── /Messages
  ├── PluginTests.cs
  └── TestConfig.cs

4. Writing Your First Test

Testing a Simple Plugin

[TestClass]
public class AccountNumberPluginTests
{
    private readonly XrmFakedContext _context;
    private readonly IOrganizationService _service;

    public AccountNumberPluginTests()
    {
        _context = new XrmFakedContext();
        _service = _context.GetOrganizationService();

        // Initialize metadata
        _context.InitializeMetadata(typeof(Account).Assembly);
    }

    [TestMethod]
    public void Should_Generate_AccountNumber_OnCreate()
    {
        // Arrange
        var target = new Account { Name = "Test Account" };
        var plugin = new AccountNumberGeneratorPlugin();

        // Act
        _context.ExecutePluginWithTarget(plugin, target);

        // Assert
        var account = _context.CreateQuery<Account>().First();
        Assert.IsTrue(account.AccountNumber.StartsWith("ACCT-"));
    }
}

Key Classes

ClassPurpose
XrmFakedContextMock execution environment
XrmFakedPluginExecutionContextMock plugin context
XrmFakedTracingServiceMock tracing service

5. Advanced Testing Scenarios

Testing with Related Records

[TestMethod]
public void Should_Update_Contact_LastOrderDate()
{
    // Arrange
    var contact = new Contact { Id = Guid.NewGuid() };
    var account = new Account 
    { 
        PrimaryContactId = contact.ToEntityReference() 
    };

    _context.Initialize(new Entity[] { contact });

    var plugin = new UpdateContactPlugin();

    // Act
    _context.ExecutePluginWithTarget(plugin, account);

    // Assert
    var updatedContact = _context.CreateQuery<Contact>().First();
    Assert.AreEqual(DateTime.Today, updatedContact.LastOrderDate);
}

Testing Plugin Pipeline Stages

var context = new XrmFakedPluginExecutionContext
{
    Stage = (int)PluginStage.PostOperation,
    MessageName = "Create"
};

_context.ExecutePluginWithContext(plugin, context, target);

Testing Custom API Messages

_context.AddExecutionMock<MyCustomRequest>(request => 
{
    return new MyCustomResponse { Result = true };
});

var response = _service.Execute(new MyCustomRequest());
Assert.IsTrue(((MyCustomResponse)response).Result);

6. Best Practices

Test Organization

  1. Arrange-Act-Assert pattern
  2. Separate test classes per plugin
  3. Reusable test data builders

Performance Optimization

  • Reuse context where possible
  • Limit metadata initialization
  • Use early-bound when possible

Assertion Techniques

// Verify plugin steps
var pluginSteps = _context.GetPluginSteps();
Assert.AreEqual(1, pluginSteps.Count);

// Verify service calls
var trace = _context.GetTracingService().DumpTrace();
Assert.IsTrue(trace.Contains("Account updated"));

7. Common Pitfalls and Solutions

IssueSolution
“Metadata not found” errorsCall InitializeMetadata()
Plugin images not workingConfigure in FakePluginExecutionContext
DateTime mismatchesUse context.UtcNow in tests
Security roles not applyingMock FakeCrmServiceClient

8. Integration with CI/CD

Sample Azure Pipeline

steps:
- task: DotNetCoreCLI@2
  inputs:
    command: 'test'
    projects: '**/*Tests.csproj'
    arguments: '--configuration Release'

Code Coverage Reporting

<PackageReference Include="coverlet.msbuild" Version="3.1.2">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>

9. Real-World Test Examples

Testing a Validation Plugin

[TestMethod]
[ExpectedException(typeof(InvalidPluginExecutionException))]
public void Should_Reject_Invalid_ZipCode()
{
    var target = new Account 
    { 
        Address1_PostalCode = "ABCDE" 
    };

    _context.ExecutePluginWithTarget(new ValidateAddressPlugin(), target);
}

Testing a Complex Workflow

[TestMethod]
public void Should_Create_FollowUp_Task()
{
    // Arrange
    var account = new Account();
    _context.Initialize(account);

    var workflow = new CreateFollowUpWorkflow();

    // Act
    _context.ExecuteWorkflow(account.Id, workflow);

    // Assert
    var tasks = _context.CreateQuery<Task>();
    Assert.AreEqual(1, tasks.Count());
    Assert.AreEqual("Follow Up", tasks.First().Subject);
}

10. Additional Resources

Learning Materials

Community Support

  • Stack Overflow (#fakexrmeasy tag)
  • GitHub Issues
  • Dynamics Community Forum

Implementation Roadmap

  1. Foundation Phase (1-2 weeks)
  • Setup test project
  • Create basic plugin tests
  • Integrate with build pipeline
  1. Maturity Phase (1-3 months)
  • Test all critical plugins
  • Implement data-driven tests
  • Add code coverage reporting
  1. Advanced Phase (ongoing)
  • Test custom APIs
  • Mock external dependencies
  • Performance optimization
// Sample cleanup
[TestCleanup]
public void Cleanup()
{
    _context?.Dispose();
}

Leave a Reply

Your email address will not be published. Required fields are marked *