Skip to content

Server-side Unit Testing

DGT-TST-010 — Use Digitall.Dataverse.Testing for plugin and Custom API unit tests. It provides an in-memory IOrganizationService (and IOrganizationServiceAsync2) implementation, so tests run fast, deterministically, and without a connection to a live Dataverse environment — which also means they run safely in CI without needing environment credentials at all for this step.

Keep your test framework choice (xUnit, NUnit, MSTest all work, since Digitall.Dataverse.Testing doesn't mandate one), but don't assert against a real environment for unit-level plugin tests — use the in-memory service instead.

dotnet add package Digitall.Dataverse.Testing

Requires .NET SDK 10.0+ for the test project itself (check the package's global.json for the exact pinned version) — this is independent of the net462 target of the plugin project under test.

Basic CRUD test

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Digitall.Dataverse.Model;     // generated early-bound model
using Digitall.Dataverse.Testing;

public class AccountTests
{
    [Test]
    public void Should_CreateAndRetrieveEntity()
    {
        var service = new FakeDataverseBuilder().GetOrganizationService();

        var account = new Account { Name = "Contoso Ltd" };

        var id = service.Create(account);
        var retrieved = service.Retrieve(Account.EntityLogicalName, id, new ColumnSet(Account.LogicalNames.Name))
                               .ToEntity<Account>();

        Assert.That(retrieved.Name, Is.EqualTo("Contoso Ltd"));
    }
}

Use early-bound entities (matching Early-Bound Models) by marking the test assembly with [assembly: Microsoft.Xrm.Sdk.Client.ProxyTypesAssembly] — the fake service auto-discovers early-bound types via reflection.

Testing a plugin end to end

FakePluginContextBuilder combines an IServiceProvider builder with an auto-configured fake organization service — this is the entry point for testing an Executor-derived plugin without touching Dataverse:

[Test]
public void Should_AcceptValidBirthDate_OnCreate()
{
    var builder = new FakePluginContextBuilder();

    var target = new Contact { BirthDate = new DateTime(1990, 1, 1) };

    var serviceProvider = builder
        .WithMessageName(SdkMessageNames.Create)
        .WithTarget(target)
        .BuildServiceProvider();

    var plugin = new ContactValidationPlugin();
    plugin.Execute(serviceProvider);

    Assert.That(plugin.Result, Is.EqualTo(ExecutionResult.Ok));
}

plugin.Result — the ExecutionResult your Execute() override returned — is directly assertable; see Patterns & Pitfalls for why returning a meaningful ExecutionResult (not always Ok) makes this assertion useful.

Pre-seeding data and metadata

var service = new FakeDataverseBuilder()
    .AddData(existingAccount, relatedContact)
    .AddEntityMetadata(accountMetadata)
    .AddRelationships(accountToContactRelationship)
    .LoadMetadata("./metadata/")
    .WithUserId(testUserId)
    .GetOrganizationService();

LoadMetadata accepts exported EntityMetadata XML, which is the practical way to get realistic metadata (attribute types, option set values) into a test without hand-constructing it — export it once from a representative environment and check it into the test project alongside the test fixtures it supports.

Testing environment variable reads

If a plugin uses Digitall.Plugins' built-in GetConfig extension (see DIGITALL Assembly), seed the corresponding environment variable definition/value via .AddConfig(key, defaultValue, value) on the builder rather than mocking the extension method itself.

Where this runs

Server-side unit tests run as a build pipeline step (step 9), after the Solution Checker — both gate the build, but the Solution Checker catches customization-level issues while these tests catch logic-level regressions in your plugins and Custom APIs.