Assert expected exceptions in a Dataverse plugin scenario
Assert expected exceptions in a Dataverse plugin scenario
Section titled “Assert expected exceptions in a Dataverse plugin scenario”A validation plugin is supposed to throw for some inputs. A red terminal alone does not tell you whether it rejected the record correctly or crashed for an unrelated reason. Declare the rejection you expect, then keep an accepted case beside it.
This how-to uses a business account without a VAT number. Success means the plugin raises the intended InvalidPluginExecutionException. With a VAT number present, success means it returns normally. The terminal also displays the plugin’s trace messages; these are not assertions. Each case is one scenario and one xg run invocation.
1. Use a small validation plugin
Section titled “1. Use a small validation plugin”You need Windows, a working XrmGhost installation, and a .NET SDK that can build the example. Create a VatValidation folder and save these four files directly inside it:
The project targets net462, references the Dataverse SDK, and builds Contoso.HowTo.dll. The plugin’s relevant branch is:
var accountType = target.GetAttributeValue<OptionSetValue>("new_accounttype");if (accountType == null || accountType.Value != 505480000) return;
var vatNumber = target.GetAttributeValue<string>("new_vatnumber");if (string.IsNullOrWhiteSpace(vatNumber)){ tracing.Trace("Business account with no VAT number; blocking save."); throw new InvalidPluginExecutionException( "VAT number is required and must be valid for business accounts.");}
tracing.Trace("Business account with a VAT number; allowing save.");The full source also guards the message, Target and entity name. It handles Create only. new_accounttype, new_vatnumber and Business value 505480000 follow the Contoso demo convention; substitute your own schema in real code.
This deliberately narrow example checks presence, not VAT format, registration numbers or tax-authority records. Despite the shared business-facing exception wording, a non-empty but malformed VAT value is outside its coverage. It is not a replacement for a complete production validator.
In PowerShell, from that folder:
dotnet build Contoso.HowTo.csproj -c Debug -p:DebugType=full2. Describe the rejected account
Section titled “2. Describe the rejected account”The supplied vat-missing.json is a complete scenario, not an output transcript:
{ "scenarioName": "Business account without VAT must be rejected", "pluginAssemblyName": "Contoso.HowTo.dll", "pluginTypeName": "Contoso.HowTo.VatRequiredPlugin", "executionContext": { "messageName": "Create", "primaryEntityName": "account", "primaryEntityId": "00000000-0000-0000-0000-000000000001", "stage": 20, "mode": 0, "inputParameters": { "Target": { "logicalName": "account", "id": "00000000-0000-0000-0000-000000000001", "attributes": { "name": "Contoso Ltd", "new_accounttype": { "value": 505480000 } } } } }, "expectedException": { "typeName": "Microsoft.Xrm.Sdk.InvalidPluginExecutionException", "messageContains": "VAT number is required and must be valid for business accounts." }}The option-set value is an object with a value key, not a bare integer. The VAT attribute is absent. Stage 20 and mode 0 describe a synchronous pre-operation context. The assembly and type entries document the scenario; the command’s -a and -t select what actually executes.
expectedException.typeName must be the exact fully qualified name. The matcher walks the exception chain and requires the type and optional, case-sensitive messageContains substring to match on the same exception. A different exception, or no exception, fails the case. An empty type name matches nothing.
3. Run the negative case
Section titled “3. Run the negative case”xg run -a bin\Debug\net462\Contoso.HowTo.dll -t Contoso.HowTo.VatRequiredPlugin -s vat-missing.json --data-source mock; $LASTEXITCODEThe captured run below reports Execution Succeeded! and Expected Exception Matched:: the rejection is the declared behaviour, not a failed check. Captured on Windows on 2026-10-29 with CLI 1.7.7 and Core 1.1.0. The final 0 is the exit code printed by $LASTEXITCODE.
Executing Plugin: Contoso.HowTo.VatRequiredPluginAssembly Path: bin\Debug\net462\Contoso.HowTo.dllScenario Loaded: Business account without VAT must be rejected--- Plugin Execution Result ---Entity Operations Summary:Total Operations: 0Successful: 0
Trace Log: PLUGIN | 1ms | Business account with no VAT number; blocking save.
Performance:[PERFORMANCE] Total: 2ms, Traces: 41 [Framework: 25, OperationTracker: 9, AssemblyResolver: 4, Plugin: 1, Diagnostic: 2]
Final Entity States: [PRO FEATURE - UPGRADE TO UNLOCK] View final entity data, plugin modifications, and newly created records. Get detailed insights into what your plugins actually changed. $ Upgrade to Pro: https://www.xrmghost.tech/#pricing
Execution Succeeded!Expected Exception Matched: Microsoft.Xrm.Sdk.InvalidPluginExecutionException (message contains: VATnumber is required and must be valid for business accounts.)Output Parameters: {}0PS C:\src\contoso-demo\howto\vat-validation>4. Keep the accepted case too
Section titled “4. Keep the accepted case too”In vat-present.json, the context is the same, with one extra Target attribute:
"new_vatnumber": "IT12345678901"There is no expectedException key.
Run it separately:
xg run -a bin\Debug\net462\Contoso.HowTo.dll -t Contoso.HowTo.VatRequiredPlugin -s vat-present.json --data-source mock; $LASTEXITCODEThe captured run completes normally with exit code 0, printed by $LASTEXITCODE. A plugin that rejects every account would pass the negative case but fail this one. Keeping both checks protects both sides of the rule.
Executing Plugin: Contoso.HowTo.VatRequiredPluginAssembly Path: bin\Debug\net462\Contoso.HowTo.dllScenario Loaded: Business account with VAT must be accepted--- Plugin Execution Result ---Entity Operations Summary:Total Operations: 0Successful: 0
Trace Log: PLUGIN | 1ms | Business account with a VAT number; allowing save.
Performance:[PERFORMANCE] Total: 1ms, Traces: 41 [Framework: 25, OperationTracker: 9, AssemblyResolver: 4, Plugin: 1, Diagnostic: 2]
Final Entity States: [PRO FEATURE - UPGRADE TO UNLOCK] View final entity data, plugin modifications, and newly created records. Get detailed insights into what your plugins actually changed. $ Upgrade to Pro: https://www.xrmghost.tech/#pricing
Execution Succeeded!Output Parameters: {}0PS C:\src\contoso-demo\howto\vat-validation>These files are repeatable local checks, not a test-suite runner or a full Dataverse simulation. Assembly-loading failures remain failures; an expected exception cannot excuse broken setup. Keep your existing unit tests and validate the deployed behaviour in the target environment. The scenario reference covers matching details; Visual Studio debugging helps when the result is not what you intended.