![]()
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();
}
