![]()
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
| Class | Purpose | 
|---|---|
XrmFakedContext | Mock execution environment | 
XrmFakedPluginExecutionContext | Mock plugin context | 
XrmFakedTracingService | Mock 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
- Arrange-Act-Assert pattern
 - Separate test classes per plugin
 - 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
| Issue | Solution | 
|---|---|
| “Metadata not found” errors | Call InitializeMetadata() | 
| Plugin images not working | Configure in FakePluginExecutionContext | 
| DateTime mismatches | Use context.UtcNow in tests | 
| Security roles not applying | Mock 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
- Foundation Phase (1-2 weeks)
 
- Setup test project
 - Create basic plugin tests
 - Integrate with build pipeline
 
- Maturity Phase (1-3 months)
 
- Test all critical plugins
 - Implement data-driven tests
 - Add code coverage reporting
 
- Advanced Phase (ongoing)
 
- Test custom APIs
 - Mock external dependencies
 - Performance optimization
 
// Sample cleanup
[TestCleanup]
public void Cleanup()
{
    _context?.Dispose();
}
	