Ask a D365 team who owns testing for the Sales module and you will get an answer. Ask who owns testing for the twelve Power Automate flows that fire when a Sales record changes, and the room usually goes quiet.
Not because anyone is being evasive. The honest answer is that nobody owns it, and the reason is structural. The D365 functional team does not own those flows because they are custom. The development team does not own them because they are not code. The person who built them is a business analyst in operations who needed a process automated and used the tool Microsoft explicitly gave them for the purpose.
Low-code removed the barrier to building. It did not remove the consequences of what gets built.
A Power Automate flow that updates a D365 record is doing the same thing a plugin does, with the same blast radius. The plugin goes through source control, code review and a deployment pipeline. The flow goes through someone clicking Save.
This piece covers why this layer goes untested, the business logic that ends up scattered across five technologies nobody has mapped, five failure modes specific to Power Platform, and what to actually validate.
Why This Layer Goes Untested
Three things combine, and the combination is more explanatory than any one of them.
The ownership vacuum. Power Platform assets sit between the functional team, the development team and the business. Each has a defensible reason it is not theirs. Testing follows ownership, so when ownership is ambiguous, testing does not happen, not through neglect, but because no one’s job description includes it.
The low-code promise implies it isn’t needed. The entire value proposition is that you do not need engineering practice to build these. That is true for building. It was never true for the consequences, but the framing carries over, if you did not need a developer to make it, it feels excessive to require a test plan for it.
It is genuinely hard to see. A plugin lives in a solution someone deployed. A flow can be built directly in production by anyone with a license, in a personal environment, connected to a service account. Governance tooling exists, and most organizations have not turned it on or do not review its output.
The result is a layer that carries real business logic, changes without change control, and has no test coverage, sitting directly beneath a D365 environment that is otherwise reasonably well governed.
The Shadow Logic Problem
Here is the thing that makes this genuinely difficult rather than merely neglected.
In a mature D365 environment, the answer to “what happens when an opportunity record is created?” can span six different mechanisms:
| Mechanism | Typically Owned By | Change Control |
| Out-of-box D365 behaviour | Microsoft | Release waves |
| Business rules | Functional consultant | Solution deployment, sometimes |
| Classic workflows (legacy) | Whoever built them years ago | Frequently none |
| Dataverse plugins / custom code | Development team | Source control and pipeline |
| Power Automate flows | Business maker | Usually none |
| Business process flows | Functional consultant | Solution deployment |
Each of those can read the same record, write to it, and trigger the others. A plugin updates a field, which fires a flow, which updates another field, which trips a business rule. Nobody designed that chain. It assembled itself over three years of individually reasonable additions.
Nobody in the organization can answer “what happens when this record is created” without opening five different tools, and most people do not know all five exist.
The testing consequence is specific. You cannot validate a D365 process by testing the D365 module, because a meaningful part of what happens is not in the module. It is in the flow that fires afterwards, owned by someone in a different department, that nobody mentioned during requirements gathering because to them it was not a system change, it was just automating their own work.
Five Failure Modes Specific to This Layer
1. Delegation limits that only appear at scale
This is the classic, and it is genuinely nasty because it is silent. Power Apps delegates queries to the data source where it can. Where it cannot, certain functions, certain operators, certain data types, it pulls a limited number of records locally and evaluates against those instead. The default is 500.
In development, against a test environment with 200 records, everything works. In production against 50,000 records, the app returns results from the first 500 and displays them confidently. No error. No warning at runtime. The maker saw a delegation warning in the studio and it looked like a suggestion.
A user reports that a customer is missing from a search. Nobody can reproduce it, because it depends on where that record falls in an unordered set.
2. Flows that fail quietly
Power Automate failures are visible in run history and almost nowhere else. A flow that has been failing on 15% of runs for three weeks produces no alert unless someone configured one, and the maker who built it may not check it, they built it, it worked, they moved on.
The business impact is a set of records that silently did not get the update they should have. Downstream reporting is wrong and nobody knows why, because the failure is in a system nobody is monitoring.
3. Trigger conditions that over- or under-fire
A flow triggered on record update fires on every update, including updates made by other flows and by the system. Without a trigger condition it can fire on changes irrelevant to its purpose, or in the worst case, on updates it made itself, producing a loop that Power Platform will eventually throttle rather than fix.
The opposite failure is subtler and more common: a trigger condition written to be selective that excludes a case it should have included, so the flow simply does not run for a subset of records. Nothing errors. The automation just quietly does not happen for some transactions.
4. Dual-write drift between F&O and Dataverse
For organizations running both Finance & Operations and Customer Engagement apps, dual-write synchronizes entities between the two. It is genuinely useful and it is another surface that fails silently, a mapping that stops working, a sync that pauses, a transformation that starts producing subtly wrong values on one side.
The failure mode is divergence: F&O and Dataverse hold different versions of the same customer or product, and neither system considers itself wrong.
5. Column-level security that nobody verified
Dataverse security operates at table, column and row level. Column-level security is powerful and frequently misconfigured, a field masked in the model-driven app that a canvas app or a flow reads without restriction, because the security profile was applied to one surface and not the other.
The result is data visible where it should not be, through a route nobody tested because the model-driven app looked correct.
Why “It Worked in Test” Means Less Here
Power Platform has real application lifecycle management, solutions, managed and unmanaged, environment variables, connection references, and pipelines. The tooling is there and it works.
What it does not do is prevent the two things that actually break deployments in this layer.
The first is that a lot of what runs in production was never in a solution. Flows built directly in the production environment, apps created in personal environments and shared, connections authenticated as an individual rather than a service principal. None of that moves through the ALM process because it never entered it.
The second is that even properly deployed solutions behave differently across environments in ways that are hard to catch. Connection references point somewhere; if they point at a different connection in production, the flow runs against different data. Environment variables that were not parameterized carry development values forward. And the data volume difference between test and production is exactly what surfaces delegation problems that were invisible in test.
The delegation bug that ruins your week was not introduced at deployment. It was always there, test just did not have enough records to reveal it.
What to Validate
Given all of that, the useful assertions cluster into five areas.
| Area | What to Assert | Catches |
| End-to-end business outcome | The record ends in the correct state after all mechanisms have fired, not just after the D365 action | Shadow logic, flows, plugins and rules interacting unexpectedly |
| Flow execution and completion | The flow ran, completed, and produced the intended data change, not merely that it was triggered | Silent failures and partial completion |
| Behaviour at production data volume | Queries and app behaviour against realistic record counts | Delegation limits and pagination assumptions |
| Security across surfaces | Column and row access enforced in the model-driven app, canvas app and via flows alike | Security applied to one surface but not others |
| Cross-system consistency | F&O and Dataverse hold matching values for dual-written entities | Sync drift and transformation errors |
The first row is the one that matters most and the hardest to do with conventional tooling. Testing the D365 form tells you the form worked. Testing the business outcome, after every flow, plugin and rule has finished, tells you the process worked, which is the only assertion that accounts for logic you did not know existed.
The third row deserves emphasis because it is so commonly skipped. If your test environment holds a fraction of production data, a category of defect is structurally invisible to you until release.
How Sofy Transforms Your Workflow
Sofy validates D365 at the business-outcome level, at the data layer, which is what makes shadow logic tractable rather than a mapping exercise.
An agent validating an opportunity-to-order process asserts on the final state of the record and its related data after everything has fired, the D365 action, whatever flows triggered, whatever plugins ran, whatever business rules applied. It does not need a map of which mechanisms exist, because it validates the outcome rather than the path. If an undocumented flow is corrupting a field, the assertion fails even though nobody knew the flow was there.
For the cross-surface security and dual-write assertions, the same data-layer approach applies, checking what a given identity can actually read through each route, and whether F&O and Dataverse agree, rather than confirming a configuration screen looks correct.
And because agents coordinate across layers multi-agent orchestration, a Power Apps interaction and the Dataverse state it produces can be validated in one run rather than as separate exercises.
What This Doesn’t Solve
Two honest limits.
Testing detects the effects of shadow logic; it does not inventory it. If an undocumented flow is corrupting data, outcome validation will surface the corruption, but somebody still has to go find the flow and decide what to do about it. The Power Platform admin centre and the Center of Excellence starter kit are the right tools for discovery, and they are worth deploying independently of any testing decision.
And the ownership gap is an organizational problem, not a tooling one. Testing gives you evidence that something in this layer is misbehaving. It does not resolve the question of whose job it is governance and ownership to fix a flow built eighteen months ago by someone who has since changed roles. That conversation is unavoidable, and having test evidence makes it a shorter one.
Frequently Asked Questions
Why is Power Platform rarely tested?
Primarily an ownership vacuum. Power Apps and Power Automate assets sit between the D365 functional team (who treat them as custom), the development team (who treat them as not-code) and the business maker who built them. Testing follows ownership, so ambiguous ownership means it does not happen. The low-code framing compounds this, if you did not need a developer to build it, requiring a test plan feels disproportionate.
What is a delegation warning in Power Apps and why does it matter?
Delegation means Power Apps pushes query work to the data source. Where a function or operator cannot be delegated, the app instead retrieves a limited set of records, 500 by default, and evaluates locally. Against a small test dataset everything appears correct. Against production volume, the app returns results from a partial set with no runtime error, so a record can be genuinely missing from a search with nothing indicating a problem.
How do you test a Power Automate flow?
Assert on the outcome rather than the trigger. Verify that the flow ran, completed rather than partially executing, and produced the intended data change in Dataverse. Flow failures are visible in run history and almost nowhere else, so a flow can fail on a proportion of runs for weeks without alerting anyone, which means testing the resulting data state is more reliable than testing the flow definition.
What is dual-write and what goes wrong with it?
Dual-write synchronizes entities between Dynamics 365 Finance & Operations and Dataverse for organizations running both. It fails quietly: a mapping stops working, a sync pauses, or a transformation begins producing subtly different values on one side. The result is divergence, two systems holding different versions of the same record, with neither considering itself incorrect.
Can you test Dataverse security properly?
Yes, and it needs testing across surfaces rather than in configuration. Dataverse enforces security at table, column and row level, and column-level security in particular is frequently applied to a model-driven app while a canvas app or a flow reads the same field unrestricted. Verifying what a given identity can actually retrieve through each route is the assertion that matters, not whether the security profile screen looks right.
The Bottom Line
Power Platform gave business users the ability to change how D365 behaves without involving engineering. That was the point, and on balance it has been good, processes get automated that would never have justified a development ticket.
What did not come with it was any of the practice that surrounds code doing the same job. No review, no pipeline in many cases, no monitoring, no tests. So a layer carrying real business logic sits beneath a reasonably well-governed D365 environment, changing without change control, and failing in ways that are specifically designed to be quiet, delegation limits, silent flow failures, sync drift.
The practical response is not to restrict the platform. It is to validate outcomes rather than paths, at production data volume, across every surface, so that logic nobody documented still has to produce the right result.
Validate the Outcome, Not the Path
See agents assert on final record state after every flow, plugin and rule has fired, including the ones nobody documented.
