Today I want to talk about something that can save you from painful production issues. If you are writing plugins for Dynamics 365, unit tests are great, but they are not enough. You need integration testing.
I have seen countless projects where plugins pass all unit tests with flying colors, get deployed to production, and then fail in ways nobody expected. The root cause is almost always the same. Unit tests with mocked contexts cannot replicate the complexity of a real Dynamics 365 environment.
Let me explain why integration testing is critical and how to implement it effectively.
What Integration Testing Means for D365 Plugins
First, let me clarify what I mean by integration testing in the context of D365 plugins.
Unit testing means testing your plugin code in isolation. You mock the IOrganizationService, you mock the IPluginExecutionContext, and you verify that your code logic works correctly. This is valuable and you should absolutely do it.
Integration testing means running your plugin against a real Dynamics 365 environment. Your plugin executes in the actual Dataverse pipeline with real database transactions, real security checks, real metadata, and real data. This is where you discover issues that mocks cannot reveal.
What Integration Tests Catch That Unit Tests Miss
Let me walk through the types of issues that only show up in integration testing.
Database Transaction Behavior
D365 plugins run within database transactions. If your plugin throws an exception in PreOperation or PostOperation, the entire transaction rolls back. This behavior is difficult to test accurately with mocks. Integration tests show you exactly what happens when transactions fail.
Pre and Post Entity Images
You can configure entity images in your plugin step registration. These images provide snapshots of data before or after the main operation. In unit tests, you manually populate these images. In integration tests, D365 populates them based on your actual step registration. If you configured the images incorrectly, your integration test will fail.
Security Roles and Permissions
Your plugin might run with different security contexts. The calling user might not have permissions to create or update certain records. Unit tests rarely check this. Integration tests running with actual user contexts will catch permission issues.
Plugin Execution Order
Multiple plugins can execute for the same message and entity. The execution order matters. Your plugin might depend on data set by another plugin, or it might conflict with another plugin. Integration tests in a realistic environment reveal these dependencies.
QueryExpression and FetchXML Behavior
If your plugin uses QueryExpression or FetchXML to retrieve data, the results depend on your Dataverse schema, security roles, and data. Small differences in how you mock these queries versus how they actually behave can cause bugs.
Metadata Dependencies
Your plugin might depend on custom fields, relationships, or option sets existing in the environment. If these are missing or configured differently, your plugin fails. Integration tests validate that your environment configuration matches what your plugin expects.
Plugin Registration Configuration
Sometimes the issue is not the code. The issue is how the plugin step is registered. Wrong stage, wrong execution mode, missing filtering attributes. Integration tests catch registration problems that unit tests cannot see.
Let me share a story from a project I worked on. The team built a plugin that updated a related Account record when an Opportunity was won. They wrote comprehensive unit tests. Everything passed. Code coverage was high. They felt confident.
They deployed to the TEST environment. The plugin triggered correctly, but it failed with a cryptic error message: “Generic SQL error.” The logs showed the error happened during the PreValidation stage.
After investigation, they discovered the issue. The plugin was registered in PreValidation, and it tried to update the Account record during that stage. But updates to related entities in PreValidation happen outside the main transaction and can cause locking issues. The plugin should have been registered in PostOperation.
An integration test would have caught this immediately. Running the plugin in a real D365 environment would have produced the same error during testing, not in production.
They fixed the registration, moved the step to PostOperation, and added integration tests to prevent similar issues in the future.
How to Implement Integration Testing
Now let me show you practical ways to implement integration testing for your D365 plugins.
Option 1: Using a Testing Framework with Real Connection
You can use frameworks like FakeXrmEasy or write custom test code that connects to a real D365 environment. Here is a simple example using Microsoft.PowerPlatform.Dataverse.Client:
using Microsoft.PowerPlatform.Dataverse.Client;using Microsoft.Xrm.Sdk;using Xunit;namespace MyPlugin.IntegrationTests{ public class AccountPluginIntegrationTests { private readonly IOrganizationService _service; public AccountPluginIntegrationTests() { // Connect to your TEST environment string connectionString = "AuthType=ClientSecret;Url=https://yourorg.crm.dynamics.com;ClientId=your-app-id;ClientSecret=your-secret"; _service = new ServiceClient(connectionString); } [Fact] public void CreateAccount_ShouldSetDefaultValues() { // Arrange var account = new Entity("account"); account["name"] = "Integration Test Account"; // Act - This triggers your plugin in the real environment Guid accountId = _service.Create(account); // Assert - Verify the plugin did what it should var createdAccount = _service.Retrieve("account", accountId, new Microsoft.Xrm.Sdk.Query.ColumnSet("name", "customfield")); Assert.NotNull(createdAccount["customfield"]); Assert.Equal("Expected Value", createdAccount["customfield"]); // Cleanup _service.Delete("account", accountId); } }}
This test creates a real Account record in your TEST environment. Your plugin executes automatically based on its registration. You verify the results and clean up.
Option 2: Dedicated Test Environment
Set up a dedicated D365 environment specifically for automated testing. This environment should mirror your production configuration but contain only test data. Your CI/CD pipeline can run integration tests against this environment automatically.
Option 3: PowerShell Automation
You can use PowerShell to deploy your plugin and run tests. Here is a script that deploys a solution and validates plugin behavior:
# Deploy solution to TEST environment$conn = Get-CrmConnection -InteractiveMode# Import solutionImport-CrmSolution -conn $conn -SolutionFilePath ".\YourSolution.zip"# Wait for import to completeStart-Sleep -Seconds 30# Create test record to trigger plugin$account = @{ "name" = "Test Account"}$accountId = New-CrmRecord -conn $conn -EntityLogicalName "account" -Fields $account# Retrieve and verify plugin executed correctly$createdAccount = Get-CrmRecord -conn $conn -EntityLogicalName "account" -Id $accountId -Fields "name","customfield"if ($createdAccount.customfield -ne "Expected Value") { Write-Error "Plugin did not execute as expected!" exit 1}# CleanupRemove-CrmRecord -conn $conn -EntityLogicalName "account" -Id $accountIdWrite-Host "Integration test passed!" -ForegroundColor Green
This can run in your Azure DevOps pipeline after deploying to TEST.
Option 4: Manual Testing Checklist
If automation is not feasible yet, at least maintain a manual integration testing checklist. Document scenarios that must be tested in a real environment before deploying to production. Assign someone to execute these tests after each deployment to TEST.
Best Practices for Integration Testing
Here are practices that work well for D365 plugin integration testing.
Maintain a Dedicated TEST Environment
Do not use your development environment for integration tests. Use a separate TEST environment that matches production configuration. This ensures your tests reflect real-world conditions.
Test with Realistic Data Volumes
If your production has millions of records, your TEST environment should have similar volumes for critical entities. Plugin performance issues often appear only at scale.
Include Security Context Variations
Test with different user roles. Create test users with limited permissions. Verify your plugin handles permission errors gracefully or runs with appropriate impersonation.
Test Plugin Execution Order
If you have multiple plugins for the same entity and message, test them together. Verify they execute in the correct order and do not conflict.
Run Before Every Deployment
Integration tests should be part of your deployment pipeline. Do not deploy to UAT or PROD without running integration tests in TEST first.
Keep Tests Fast and Isolated
Each test should clean up after itself. Tests should not depend on each other. Fast tests mean you can run them frequently.
Monitor and Alert
If you run automated integration tests, set up alerts for failures. You want to know immediately if a deployment breaks something.
The Cost vs. Benefit
I know what you might be thinking. Integration testing requires infrastructure. You need a TEST environment. You need service principals for automated connections. You need time to write tests.
All of this is true. But consider the alternative. A production bug that takes down critical business processes. Hours or days of troubleshooting. Emergency hotfixes. Frustrated users. Damaged credibility.
One production incident prevented by integration testing justifies the entire investment. And after you set up the infrastructure once, adding new tests is straightforward.
Wrapping Up
Unit tests validate your code logic. Integration tests validate that your plugin works in the real Dynamics 365 environment. You need both.
Integration testing catches issues with transactions, entity images, security, plugin registration, and environment configuration. These are issues that mocks cannot simulate accurately.
Setting up integration testing takes effort upfront, but it pays off every single time it catches a bug before production. Your deployments become predictable. Your confidence increases. Your production incidents decrease.
If you are not doing integration testing yet, start small. Pick your most critical plugin. Write one integration test that runs in your TEST environment. Expand from there. The investment is worth it.
Leave a comment