Unit Testing Dynamics 365 Plugins When Your Test Suite Is Broken

A technical guide for plugin developers locked on an old testing framework with a release due: what modern FakeXrmEasy versions require, testable logic behind thin interfaces, a minimal hand-rolled fake and the refactoring order that gets the critical path green first.

If your Dynamics 365 plugin tests broke on an old FakeXrmEasy version and you cannot add dependencies before a release, you do not need a framework to get tests green. Modern FakeXrmEasy, version 2 and later, uses new version specific packages, targets both .NET Framework and modern .NET and has a licence model you must check for commercial use, so upgrading is a decision, not a quick fix. Instead, move plugin logic into plain classes behind a thin interface over IOrganizationService, test those classes directly, and write a small hand-rolled fake covering only the messages you call. Fix the plugins on the release's critical path first, quarantine the rest visibly, and add tests incrementally with every change.

Why do Dynamics 365 plugin test suites break?

Usually not because the plugins changed, but because everything around the tests did. The common causes are a test project that depends on the legacy version 1 FakeXrmEasy packages while the plugin project has moved to newer Dataverse SDK assemblies, a build agent or developer machine upgraded to a newer .NET SDK or test runner, a package source that no longer serves an old package version, and assembly binding conflicts between the SDK versions the fake framework and the plugins each expect. Add tests that asserted on internal behaviour of the fake framework rather than on business outcomes, and a single upgrade can turn a whole suite red.

The situation this page addresses is the awkward one: an old framework version pinned in place, a rule or a policy that stops you adding new packages, and a release date that will not move. The goal is not a perfect test suite. It is trustworthy tests on the code that is shipping, and a path to the rest. If the plugins themselves are part of the problem, such as the wrong stage, heavy synchronous work or missing depth checks, the design fixes are in our guide to common Dynamics 365 customization mistakes.

What do modern FakeXrmEasy versions require?

FakeXrmEasy is a widely used open source framework that fakes an in-memory Dataverse organization for tests. Its later major versions are a different product from the version 1 packages many older suites use, and the differences matter when you are under deadline. The details below change, so check the current FakeXrmEasy documentation and licence terms before you plan an upgrade.

TopicVersion 1 (legacy)Version 2 and later
PackagesLegacy packages per Dynamics CRM versionNew version specific packages, such as FakeXrmEasy.v9 for Dataverse, plus modular core and plugin packages
Target frameworks.NET Framework.NET Framework (4.6.2 or later) and modern .NET, so tests can run on current build agents
Set upA context object created directly in each testA middleware style builder where you add the capabilities your tests need
LicenceOpen source under its original termsA licence model where commercial use needs a commercial licence; check the current terms before using it in a commercial project
MaintenanceNo longer the focus of developmentActively developed
  • Upgrading means rewriting test set up code, adding packages and resolving the licence question, which rarely fits inside a release that is already late.
  • It can still be the right medium-term choice for a large suite, particularly for teams that want broad message coverage and query support without maintaining fakes.
  • Whatever you choose, the refactoring below makes tests less dependent on any framework, so a later upgrade or a switch touches less code.

How do you make plugin logic testable without any framework?

Split every plugin into three parts, and put almost all the code in the part that knows nothing about Dataverse.

  • The plugin class stays thin. Execute reads the IPluginExecutionContext, the Target and any images, creates the organization service from the service factory, builds the collaborators and calls the logic. No business decisions live here.
  • The logic class is plain C#. It takes simple inputs, such as the values it needs from the Target and pre-image, and an interface for the data it must read or write. It returns a result or calls the interface. It has no reference to IServiceProvider or the execution context, so a test can construct it in one line.
  • A thin repository interface sits between the logic and IOrganizationService, with methods named for what the logic needs, such as GetActiveContract(accountId) or CreateFollowUpTask(caseId, dueDate). Its real implementation is the only code that builds QueryExpression or FetchXML and calls the service.
  • IOrganizationService is already an interface, but it is a wide one, and faking it means understanding queries. A narrow repository interface can be faked with a few lines of in-memory lists, which is why most logic tests need no fake organization service at all.
  • Keep tracing through ITracingService in the plugin and pass a small logging interface to the logic where it needs to trace; what to trace is covered in our plugin logging and diagnostics guide.

How do you build a minimal hand-rolled fake of IOrganizationService?

Implement the interface in the test project with an in-memory store, and support only the operations your plugins actually call. IOrganizationService has eight members: Create, Retrieve, Update, Delete, RetrieveMultiple, Execute, Associate and Disassociate. For most plugins, four of them matter.

MemberWhat the fake doesKeep it honest by
CreateAssigns an Id if none is set, stores a copy of the entity in a dictionary keyed by logical name and Id, returns the IdStoring a copy, so later changes to the object in the test do not change the stored row
RetrieveReturns a copy of the stored entity with only the requested columns, or throws if the row does not existRespecting the ColumnSet, so code that forgets to request a column fails in tests as it would in Dataverse
UpdateMerges the supplied attributes into the stored row, or throws if it does not existThrowing when the Id is missing, as the platform does
RetrieveMultipleSupports QueryExpression with the simple equality conditions your code uses and returns an EntityCollectionThrowing NotSupportedException for any operator, link or query type it does not implement
Delete, Execute, Associate, DisassociateThrow NotImplementedException until a plugin needs themAdding support only when a test fails for that reason
  • Record every call in a list, so tests can assert that the plugin created exactly one task or made no update at all.
  • If code calls Execute with a small number of requests, handle those request types explicitly by name and throw for everything else.
  • Do not grow the fake into a Dataverse emulator. If you find yourself implementing link entities, aggregates or security, that is the point where a maintained framework earns its licence, or where the test belongs against a real development environment.

How do you fake IServiceProvider and IPluginExecutionContext?

With plain classes, for the plugin entry point tests that check wiring rather than logic. A fake service provider is a class implementing IServiceProvider whose GetService returns the fake execution context, a fake IOrganizationServiceFactory that returns your fake organization service, and a fake ITracingService that collects trace lines in a list for assertions.

The fake execution context implements IPluginExecutionContext with settable properties: MessageName, PrimaryEntityName, Stage, Mode, Depth, UserId and InitiatingUserId, plus InputParameters, OutputParameters, SharedVariables, PreEntityImages and PostEntityImages as the SDK collection types. It is tedious to type once and trivial afterwards. Newer SDK versions add numbered context interfaces with extra members; implement only the one your plugins actually cast to. A small builder in the test project, such as a method that creates an Update context for a given Target and pre-image, keeps each test to the lines that matter.

Entry point tests should be few: one per registered step, proving that the plugin reads the right Target and image, exits on the wrong message or excessive depth, and calls the logic. The behaviour is tested on the logic classes.

In what order should you refactor to get tests green before the release?

Critical path first, and nothing deleted silently. The order below gets reliable tests onto the shipping code within the release window and leaves a visible list for afterwards.

  • Make the build honest: mark the broken tests as ignored with a reason and a tracking item rather than deleting them, so the pipeline is green and the debt is visible.
  • List the plugins changed in this release and the processes they touch, and rank them by the damage a defect would cause.
  • For the top plugin, write a few characterisation tests of the current behaviour through the entry point with the hand-rolled fakes, before changing any code.
  • Extract the logic into a plain class behind a repository interface, keeping the characterisation tests passing.
  • Add focused tests on the logic class for the rules the release changes, including the edge cases that caused past incidents.
  • Repeat for the next plugin on the list until the release scope is covered, then run the tests in the build pipeline on every commit.
  • After the release, retire ignored tests one at a time: rewrite each against the logic classes or delete it with a recorded reason, and add tests to every plugin as it is next changed.

What can plugin unit tests not prove?

Unit tests prove your logic. They do not prove how Dataverse runs it. Registration details, such as the message, stage, filtering attributes and image columns, live in the solution rather than the code. Transactions and rollback, sandbox isolation limits, real security privileges, other plugins and flows firing on the same change, and platform behaviour under load all sit outside an in-memory fake.

Cover those with a small number of scripted tests against a development environment that runs after deployment, check registration as part of solution review, and design the plugin so production failures can be diagnosed from trace output. Keep this layer thin: its job is to prove the wiring, while the fast unit tests prove the rules. For deploying plugin assemblies and solutions through a pipeline, our Power Platform ALM guide covers the tooling.

Should you upgrade FakeXrmEasy, stay on the old version or drop it?

Decide after the release, with the facts in front of you, not during it.

OptionSuitsWatch for
Stay on version 1 for nowA suite that still builds on your agents and covers code you rarely changeIt is legacy; pin versions and plan an exit before an SDK or build upgrade breaks it again
Upgrade to a current versionLarge suites that rely on query and message support you do not want to maintainLicence terms for commercial use, rewriting set up code and adding packages; check current terms first
Hand-rolled fakes and plain logic testsTeams that cannot add dependencies or use a narrow set of messagesThe fake growing into an emulator; keep it small and let it throw
A mixMost real codebasesKeep logic tests framework free, so the framework question only affects entry point tests

Is testable plugin code a reason to reconsider the platform?

No. Plugins in Dynamics 365 and Dataverse are ordinary C#, and the separation above is the same discipline any well tested server code uses. A broken test suite is a maintenance problem, and it is usually fixed faster than it looks once the logic is out of the plugin classes. What is worth reviewing is whether each plugin should be a plugin at all, because some logic belongs in a business rule, a flow or configuration.

We recommend the right solution - whether that's Microsoft Dynamics 365, Power Platform, or a custom-built CRM. Some businesses need the Microsoft ecosystem. Others need full control without licensing. We deliver both. Where an application is mostly custom server logic that Microsoft licensing adds little to, a custom-built CRM on React, Node.js, PostgreSQL or .NET puts all of it under your own test tooling.

How does Solzet help teams with broken plugin test suites?

We start from the release: which plugins ship, what they touch, and which tests are red and why. Then we quarantine visibly, add characterisation tests on the critical path, extract the logic behind narrow interfaces with hand-rolled fakes that need no new dependencies, and wire the tests into the pipeline. After the release we help you decide between staying, upgrading FakeXrmEasy after a licence review, or keeping framework free tests, and work through the backlog plugin by plugin.

The work is done by senior consultants and full-stack developers delivering remotely from Yerevan, Armenia, with 8+ years of Dynamics 365 Customer Engagement and Power Platform work, directly for your team or white-label for Microsoft partners and ISVs. Hands-on plugin development capacity is described on our Dynamics 365 developer service page.

What do people ask us?

Why did our FakeXrmEasy plugin tests stop working?

Most often because something around the tests changed: the plugins moved to newer Dataverse SDK assemblies while the tests still use the legacy version 1 packages, the build agent moved to a newer .NET SDK or test runner, an old package version became unavailable, or assembly versions conflict. Check the build output for binding and restore errors before debugging individual tests.

What does upgrading to a current FakeXrmEasy version involve?

Version 2 and later use new version specific packages, such as FakeXrmEasy.v9, support .NET Framework and modern .NET, set up tests through a builder, and use a licence model in which commercial use needs a commercial licence. Expect to rewrite test set up code and add packages, and check the current licence terms and documentation before committing to it.

Can I unit test Dynamics 365 plugins without FakeXrmEasy?

Yes. Keep the plugin class thin, move business logic into plain C# classes that take simple inputs and a narrow repository interface, and test those classes with in-memory fakes. For the few tests of the plugin entry point, write a small fake IOrganizationService, IServiceProvider and IPluginExecutionContext in the test project.

Which IOrganizationService methods does a hand-rolled fake need?

Only the ones your plugins call, which for most plugins are Create, Retrieve, Update and RetrieveMultiple with simple QueryExpression conditions. Store copies of entities in memory, respect the requested columns, record calls for assertions, and throw NotImplementedException or NotSupportedException for everything else so gaps are obvious.

How do I get plugin tests green before a release without hiding problems?

Mark broken tests as ignored with a reason and a tracking item rather than deleting them. Rank the plugins in the release by risk, write characterisation tests for the top ones, extract their logic behind interfaces and add focused tests, then run the tests in the pipeline. Retire the ignored tests one at a time after the release.

Should plugin tests run against a real Dataverse environment?

A few should. Unit tests prove logic but not registration, transactions, security, sandbox limits or interaction with other plugins and flows. Keep a small set of scripted tests against a development environment after deployment, and keep the large, fast set in memory.

Is it worth wrapping IOrganizationService when it is already an interface?

Yes, for logic tests. IOrganizationService is wide and query based, so faking it well means reimplementing query behaviour. A repository interface with methods named for what the logic needs can be faked with a few lines of in-memory data, and it keeps QueryExpression and FetchXML in one place.

Which solution is right for your business?

Tell us what you need. A senior consultant replies within one business day with a recommendation - Dynamics 365, Power Platform, or a custom-built CRM - not a sales script.